xref: /openbmc/bmcweb/redfish-core/include/event_service_manager.hpp (revision 788b091ba611d000b3cac9ad687f257005c990e1)
1 /*
2 Copyright (c) 2020 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 #include "dbus_utility.hpp"
18 #include "error_messages.hpp"
19 #include "event_matches_filter.hpp"
20 #include "event_service_store.hpp"
21 #include "filter_expr_executor.hpp"
22 #include "generated/enums/event.hpp"
23 #include "generated/enums/log_entry.hpp"
24 #include "http_client.hpp"
25 #include "metric_report.hpp"
26 #include "ossl_random.hpp"
27 #include "persistent_data.hpp"
28 #include "registries.hpp"
29 #include "registries_selector.hpp"
30 #include "str_utility.hpp"
31 #include "utility.hpp"
32 #include "utils/json_utils.hpp"
33 #include "utils/time_utils.hpp"
34 
35 #include <sys/inotify.h>
36 
37 #include <boost/asio/io_context.hpp>
38 #include <boost/circular_buffer.hpp>
39 #include <boost/container/flat_map.hpp>
40 #include <boost/url/format.hpp>
41 #include <boost/url/url_view_base.hpp>
42 #include <sdbusplus/bus/match.hpp>
43 
44 #include <algorithm>
45 #include <cstdlib>
46 #include <ctime>
47 #include <format>
48 #include <fstream>
49 #include <memory>
50 #include <ranges>
51 #include <span>
52 #include <string>
53 
54 namespace redfish
55 {
56 
57 static constexpr const char* eventFormatType = "Event";
58 static constexpr const char* metricReportFormatType = "MetricReport";
59 
60 static constexpr const char* subscriptionTypeSSE = "SSE";
61 static constexpr const char* eventServiceFile =
62     "/var/lib/bmcweb/eventservice_config.json";
63 
64 static constexpr const uint8_t maxNoOfSubscriptions = 20;
65 static constexpr const uint8_t maxNoOfSSESubscriptions = 10;
66 
67 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
68 static std::optional<boost::asio::posix::stream_descriptor> inotifyConn;
69 static constexpr const char* redfishEventLogDir = "/var/log";
70 static constexpr const char* redfishEventLogFile = "/var/log/redfish";
71 static constexpr const size_t iEventSize = sizeof(inotify_event);
72 
73 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
74 static int inotifyFd = -1;
75 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
76 static int dirWatchDesc = -1;
77 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
78 static int fileWatchDesc = -1;
79 struct EventLogObjectsType
80 {
81     std::string id;
82     std::string timestamp;
83     std::string messageId;
84     std::vector<std::string> messageArgs;
85 };
86 
87 namespace registries
88 {
89 static const Message*
90     getMsgFromRegistry(const std::string& messageKey,
91                        const std::span<const MessageEntry>& registry)
92 {
93     std::span<const MessageEntry>::iterator messageIt = std::ranges::find_if(
94         registry, [&messageKey](const MessageEntry& messageEntry) {
95             return messageKey == messageEntry.first;
96         });
97     if (messageIt != registry.end())
98     {
99         return &messageIt->second;
100     }
101 
102     return nullptr;
103 }
104 
105 static const Message* formatMessage(std::string_view messageID)
106 {
107     // Redfish MessageIds are in the form
108     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
109     // the right Message
110     std::vector<std::string> fields;
111     fields.reserve(4);
112 
113     bmcweb::split(fields, messageID, '.');
114     if (fields.size() != 4)
115     {
116         return nullptr;
117     }
118     const std::string& registryName = fields[0];
119     const std::string& messageKey = fields[3];
120 
121     // Find the right registry and check it for the MessageKey
122     return getMsgFromRegistry(messageKey, getRegistryFromPrefix(registryName));
123 }
124 } // namespace registries
125 
126 namespace event_log
127 {
128 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID)
129 {
130     static time_t prevTs = 0;
131     static int index = 0;
132 
133     // Get the entry timestamp
134     std::time_t curTs = 0;
135     std::tm timeStruct = {};
136     std::istringstream entryStream(logEntry);
137     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
138     {
139         curTs = std::mktime(&timeStruct);
140         if (curTs == -1)
141         {
142             return false;
143         }
144     }
145     // If the timestamp isn't unique, increment the index
146     index = (curTs == prevTs) ? index + 1 : 0;
147 
148     // Save the timestamp
149     prevTs = curTs;
150 
151     entryID = std::to_string(curTs);
152     if (index > 0)
153     {
154         entryID += "_" + std::to_string(index);
155     }
156     return true;
157 }
158 
159 inline int getEventLogParams(const std::string& logEntry,
160                              std::string& timestamp, std::string& messageID,
161                              std::vector<std::string>& messageArgs)
162 {
163     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
164     // First get the Timestamp
165     size_t space = logEntry.find_first_of(' ');
166     if (space == std::string::npos)
167     {
168         BMCWEB_LOG_ERROR("EventLog Params: could not find first space: {}",
169                          logEntry);
170         return -EINVAL;
171     }
172     timestamp = logEntry.substr(0, space);
173     // Then get the log contents
174     size_t entryStart = logEntry.find_first_not_of(' ', space);
175     if (entryStart == std::string::npos)
176     {
177         BMCWEB_LOG_ERROR("EventLog Params: could not find log contents: {}",
178                          logEntry);
179         return -EINVAL;
180     }
181     std::string_view entry(logEntry);
182     entry.remove_prefix(entryStart);
183     // Use split to separate the entry into its fields
184     std::vector<std::string> logEntryFields;
185     bmcweb::split(logEntryFields, entry, ',');
186     // We need at least a MessageId to be valid
187     if (logEntryFields.empty())
188     {
189         BMCWEB_LOG_ERROR("EventLog Params: could not find entry fields: {}",
190                          logEntry);
191         return -EINVAL;
192     }
193     messageID = logEntryFields[0];
194 
195     // Get the MessageArgs from the log if there are any
196     if (logEntryFields.size() > 1)
197     {
198         const std::string& messageArgsStart = logEntryFields[1];
199         // If the first string is empty, assume there are no MessageArgs
200         if (!messageArgsStart.empty())
201         {
202             messageArgs.assign(logEntryFields.begin() + 1,
203                                logEntryFields.end());
204         }
205     }
206 
207     return 0;
208 }
209 
210 inline int formatEventLogEntry(
211     const std::string& logEntryID, const std::string& messageID,
212     const std::span<std::string_view> messageArgs, std::string timestamp,
213     const std::string& customText, nlohmann::json::object_t& logEntryJson)
214 {
215     // Get the Message from the MessageRegistry
216     const registries::Message* message = registries::formatMessage(messageID);
217 
218     if (message == nullptr)
219     {
220         return -1;
221     }
222 
223     std::string msg =
224         redfish::registries::fillMessageArgs(messageArgs, message->message);
225     if (msg.empty())
226     {
227         return -1;
228     }
229 
230     // Get the Created time from the timestamp. The log timestamp is in
231     // RFC3339 format which matches the Redfish format except for the
232     // fractional seconds between the '.' and the '+', so just remove them.
233     std::size_t dot = timestamp.find_first_of('.');
234     std::size_t plus = timestamp.find_first_of('+', dot);
235     if (dot != std::string::npos && plus != std::string::npos)
236     {
237         timestamp.erase(dot, plus - dot);
238     }
239 
240     // Fill in the log entry with the gathered data
241     logEntryJson["EventId"] = logEntryID;
242 
243     logEntryJson["Severity"] = message->messageSeverity;
244     logEntryJson["Message"] = std::move(msg);
245     logEntryJson["MessageId"] = messageID;
246     logEntryJson["MessageArgs"] = messageArgs;
247     logEntryJson["EventTimestamp"] = std::move(timestamp);
248     logEntryJson["Context"] = customText;
249     return 0;
250 }
251 
252 } // namespace event_log
253 
254 class Subscription
255 {
256   public:
257     Subscription(const Subscription&) = delete;
258     Subscription& operator=(const Subscription&) = delete;
259     Subscription(Subscription&&) = delete;
260     Subscription& operator=(Subscription&&) = delete;
261 
262     Subscription(const persistent_data::UserSubscription& userSubIn,
263                  const boost::urls::url_view_base& url,
264                  boost::asio::io_context& ioc) :
265         userSub(userSubIn), policy(std::make_shared<crow::ConnectionPolicy>())
266     {
267         userSub.destinationUrl = url;
268         client.emplace(ioc, policy);
269         // Subscription constructor
270         policy->invalidResp = retryRespHandler;
271     }
272 
273     explicit Subscription(crow::sse_socket::Connection& connIn) :
274         sseConn(&connIn)
275     {}
276 
277     ~Subscription() = default;
278 
279     bool sendEventToSubscriber(std::string&& msg)
280     {
281         persistent_data::EventServiceConfig eventServiceConfig =
282             persistent_data::EventServiceStore::getInstance()
283                 .getEventServiceConfig();
284         if (!eventServiceConfig.enabled)
285         {
286             return false;
287         }
288 
289         if (client)
290         {
291             client->sendData(std::move(msg), userSub.destinationUrl,
292                              static_cast<ensuressl::VerifyCertificate>(
293                                  userSub.verifyCertificate),
294                              userSub.httpHeaders,
295                              boost::beast::http::verb::post);
296             return true;
297         }
298 
299         if (sseConn != nullptr)
300         {
301             eventSeqNum++;
302             sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
303         }
304         return true;
305     }
306 
307     bool sendTestEventLog()
308     {
309         nlohmann::json::array_t logEntryArray;
310         nlohmann::json& logEntryJson = logEntryArray.emplace_back();
311 
312         logEntryJson["EventId"] = "TestID";
313         logEntryJson["Severity"] = log_entry::EventSeverity::OK;
314         logEntryJson["Message"] = "Generated test event";
315         logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog";
316         // MemberId is 0 : since we are sending one event record.
317         logEntryJson["MemberId"] = "0";
318         logEntryJson["MessageArgs"] = nlohmann::json::array();
319         logEntryJson["EventTimestamp"] =
320             redfish::time_utils::getDateTimeOffsetNow().first;
321         logEntryJson["Context"] = userSub.customText;
322 
323         nlohmann::json msg;
324         msg["@odata.type"] = "#Event.v1_4_0.Event";
325         msg["Id"] = std::to_string(eventSeqNum);
326         msg["Name"] = "Event Log";
327         msg["Events"] = logEntryArray;
328 
329         std::string strMsg =
330             msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
331         return sendEventToSubscriber(std::move(strMsg));
332     }
333 
334     void filterAndSendEventLogs(
335         const std::vector<EventLogObjectsType>& eventRecords)
336     {
337         nlohmann::json::array_t logEntryArray;
338         for (const EventLogObjectsType& logEntry : eventRecords)
339         {
340             std::vector<std::string_view> messageArgsView(
341                 logEntry.messageArgs.begin(), logEntry.messageArgs.end());
342 
343             nlohmann::json::object_t bmcLogEntry;
344             if (event_log::formatEventLogEntry(
345                     logEntry.id, logEntry.messageId, messageArgsView,
346                     logEntry.timestamp, userSub.customText, bmcLogEntry) != 0)
347             {
348                 BMCWEB_LOG_DEBUG("Read eventLog entry failed");
349                 continue;
350             }
351 
352             if (!eventMatchesFilter(userSub, bmcLogEntry, ""))
353             {
354                 BMCWEB_LOG_DEBUG("Event {} did not match the filter",
355                                  nlohmann::json(bmcLogEntry).dump());
356                 continue;
357             }
358 
359             if (filter)
360             {
361                 if (!memberMatches(bmcLogEntry, *filter))
362                 {
363                     BMCWEB_LOG_DEBUG("Filter didn't match");
364                     continue;
365                 }
366             }
367 
368             logEntryArray.emplace_back(std::move(bmcLogEntry));
369         }
370 
371         if (logEntryArray.empty())
372         {
373             BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
374             return;
375         }
376 
377         nlohmann::json msg;
378         msg["@odata.type"] = "#Event.v1_4_0.Event";
379         msg["Id"] = std::to_string(eventSeqNum);
380         msg["Name"] = "Event Log";
381         msg["Events"] = std::move(logEntryArray);
382         std::string strMsg =
383             msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
384         sendEventToSubscriber(std::move(strMsg));
385         eventSeqNum++;
386     }
387 
388     void filterAndSendReports(const std::string& reportId,
389                               const telemetry::TimestampReadings& var)
390     {
391         boost::urls::url mrdUri = boost::urls::format(
392             "/redfish/v1/TelemetryService/MetricReportDefinitions/{}",
393             reportId);
394 
395         // Empty list means no filter. Send everything.
396         if (!userSub.metricReportDefinitions.empty())
397         {
398             if (std::ranges::find(userSub.metricReportDefinitions,
399                                   mrdUri.buffer()) ==
400                 userSub.metricReportDefinitions.end())
401             {
402                 return;
403             }
404         }
405 
406         nlohmann::json msg;
407         if (!telemetry::fillReport(msg, reportId, var))
408         {
409             BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
410                              "Report with id {}",
411                              reportId);
412             return;
413         }
414 
415         // Context is set by user during Event subscription and it must be
416         // set for MetricReport response.
417         if (!userSub.customText.empty())
418         {
419             msg["Context"] = userSub.customText;
420         }
421 
422         std::string strMsg =
423             msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
424         sendEventToSubscriber(std::move(strMsg));
425     }
426 
427     void updateRetryConfig(uint32_t retryAttempts,
428                            uint32_t retryTimeoutInterval)
429     {
430         if (policy == nullptr)
431         {
432             BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
433             return;
434         }
435         policy->maxRetryAttempts = retryAttempts;
436         policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
437     }
438 
439     uint64_t getEventSeqNum() const
440     {
441         return eventSeqNum;
442     }
443 
444     void setSubscriptionId(const std::string& id2)
445     {
446         BMCWEB_LOG_DEBUG("Subscription ID: {}", id2);
447         subId = id2;
448     }
449 
450     std::string getSubscriptionId()
451     {
452         return subId;
453     }
454 
455     bool matchSseId(const crow::sse_socket::Connection& thisConn)
456     {
457         return &thisConn == sseConn;
458     }
459 
460     // Check used to indicate what response codes are valid as part of our retry
461     // policy.  2XX is considered acceptable
462     static boost::system::error_code retryRespHandler(unsigned int respCode)
463     {
464         BMCWEB_LOG_DEBUG(
465             "Checking response code validity for SubscriptionEvent");
466         if ((respCode < 200) || (respCode >= 300))
467         {
468             return boost::system::errc::make_error_code(
469                 boost::system::errc::result_out_of_range);
470         }
471 
472         // Return 0 if the response code is valid
473         return boost::system::errc::make_error_code(
474             boost::system::errc::success);
475     }
476 
477     persistent_data::UserSubscription userSub;
478 
479   private:
480     std::string subId;
481     uint64_t eventSeqNum = 1;
482     boost::urls::url host;
483     std::shared_ptr<crow::ConnectionPolicy> policy;
484     crow::sse_socket::Connection* sseConn = nullptr;
485 
486     std::optional<crow::HttpClient> client;
487 
488   public:
489     std::optional<filter_ast::LogicalAnd> filter;
490 };
491 
492 class EventServiceManager
493 {
494   private:
495     bool serviceEnabled = false;
496     uint32_t retryAttempts = 0;
497     uint32_t retryTimeoutInterval = 0;
498 
499     std::streampos redfishLogFilePosition{0};
500     size_t noOfEventLogSubscribers{0};
501     size_t noOfMetricReportSubscribers{0};
502     std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor;
503     boost::container::flat_map<std::string, std::shared_ptr<Subscription>>
504         subscriptionsMap;
505 
506     uint64_t eventId{1};
507 
508     struct Event
509     {
510         std::string id;
511         nlohmann::json message;
512     };
513 
514     constexpr static size_t maxMessages = 200;
515     boost::circular_buffer<Event> messages{maxMessages};
516 
517     boost::asio::io_context& ioc;
518 
519   public:
520     EventServiceManager(const EventServiceManager&) = delete;
521     EventServiceManager& operator=(const EventServiceManager&) = delete;
522     EventServiceManager(EventServiceManager&&) = delete;
523     EventServiceManager& operator=(EventServiceManager&&) = delete;
524     ~EventServiceManager() = default;
525 
526     explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn)
527     {
528         // Load config from persist store.
529         initConfig();
530     }
531 
532     static EventServiceManager&
533         getInstance(boost::asio::io_context* ioc = nullptr)
534     {
535         static EventServiceManager handler(*ioc);
536         return handler;
537     }
538 
539     void initConfig()
540     {
541         loadOldBehavior();
542 
543         persistent_data::EventServiceConfig eventServiceConfig =
544             persistent_data::EventServiceStore::getInstance()
545                 .getEventServiceConfig();
546 
547         serviceEnabled = eventServiceConfig.enabled;
548         retryAttempts = eventServiceConfig.retryAttempts;
549         retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval;
550 
551         for (const auto& it : persistent_data::EventServiceStore::getInstance()
552                                   .subscriptionsConfigMap)
553         {
554             const persistent_data::UserSubscription& newSub = it.second;
555 
556             boost::system::result<boost::urls::url> url =
557                 boost::urls::parse_absolute_uri(newSub.destinationUrl);
558 
559             if (!url)
560             {
561                 BMCWEB_LOG_ERROR(
562                     "Failed to validate and split destination url");
563                 continue;
564             }
565             std::shared_ptr<Subscription> subValue =
566                 std::make_shared<Subscription>(newSub, *url, ioc);
567 
568             subscriptionsMap.insert(std::pair(subValue->userSub.id, subValue));
569 
570             updateNoOfSubscribersCount();
571 
572             if constexpr (!BMCWEB_REDFISH_DBUS_LOG)
573             {
574                 cacheRedfishLogFile();
575             }
576 
577             // Update retry configuration.
578             subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
579         }
580     }
581 
582     static void loadOldBehavior()
583     {
584         std::ifstream eventConfigFile(eventServiceFile);
585         if (!eventConfigFile.good())
586         {
587             BMCWEB_LOG_DEBUG("Old eventService config not exist");
588             return;
589         }
590         auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false);
591         if (jsonData.is_discarded())
592         {
593             BMCWEB_LOG_ERROR("Old eventService config parse error.");
594             return;
595         }
596 
597         const nlohmann::json::object_t* obj =
598             jsonData.get_ptr<const nlohmann::json::object_t*>();
599         for (const auto& item : *obj)
600         {
601             if (item.first == "Configuration")
602             {
603                 persistent_data::EventServiceStore::getInstance()
604                     .getEventServiceConfig()
605                     .fromJson(item.second);
606             }
607             else if (item.first == "Subscriptions")
608             {
609                 for (const auto& elem : item.second)
610                 {
611                     std::optional<persistent_data::UserSubscription>
612                         newSubscription =
613                             persistent_data::UserSubscription::fromJson(elem,
614                                                                         true);
615                     if (!newSubscription)
616                     {
617                         BMCWEB_LOG_ERROR("Problem reading subscription "
618                                          "from old persistent store");
619                         continue;
620                     }
621                     persistent_data::UserSubscription& newSub =
622                         *newSubscription;
623 
624                     std::uniform_int_distribution<uint32_t> dist(0);
625                     bmcweb::OpenSSLGenerator gen;
626 
627                     std::string id;
628 
629                     int retry = 3;
630                     while (retry != 0)
631                     {
632                         id = std::to_string(dist(gen));
633                         if (gen.error())
634                         {
635                             retry = 0;
636                             break;
637                         }
638                         newSub.id = id;
639                         auto inserted =
640                             persistent_data::EventServiceStore::getInstance()
641                                 .subscriptionsConfigMap.insert(
642                                     std::pair(id, newSub));
643                         if (inserted.second)
644                         {
645                             break;
646                         }
647                         --retry;
648                     }
649 
650                     if (retry <= 0)
651                     {
652                         BMCWEB_LOG_ERROR(
653                             "Failed to generate random number from old "
654                             "persistent store");
655                         continue;
656                     }
657                 }
658             }
659 
660             persistent_data::getConfig().writeData();
661             std::error_code ec;
662             std::filesystem::remove(eventServiceFile, ec);
663             if (ec)
664             {
665                 BMCWEB_LOG_DEBUG(
666                     "Failed to remove old event service file.  Ignoring");
667             }
668             else
669             {
670                 BMCWEB_LOG_DEBUG("Remove old eventservice config");
671             }
672         }
673     }
674 
675     void updateSubscriptionData() const
676     {
677         persistent_data::EventServiceStore::getInstance()
678             .eventServiceConfig.enabled = serviceEnabled;
679         persistent_data::EventServiceStore::getInstance()
680             .eventServiceConfig.retryAttempts = retryAttempts;
681         persistent_data::EventServiceStore::getInstance()
682             .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval;
683 
684         persistent_data::getConfig().writeData();
685     }
686 
687     void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg)
688     {
689         bool updateConfig = false;
690         bool updateRetryCfg = false;
691 
692         if (serviceEnabled != cfg.enabled)
693         {
694             serviceEnabled = cfg.enabled;
695             if (serviceEnabled && noOfMetricReportSubscribers != 0U)
696             {
697                 registerMetricReportSignal();
698             }
699             else
700             {
701                 unregisterMetricReportSignal();
702             }
703             updateConfig = true;
704         }
705 
706         if (retryAttempts != cfg.retryAttempts)
707         {
708             retryAttempts = cfg.retryAttempts;
709             updateConfig = true;
710             updateRetryCfg = true;
711         }
712 
713         if (retryTimeoutInterval != cfg.retryTimeoutInterval)
714         {
715             retryTimeoutInterval = cfg.retryTimeoutInterval;
716             updateConfig = true;
717             updateRetryCfg = true;
718         }
719 
720         if (updateConfig)
721         {
722             updateSubscriptionData();
723         }
724 
725         if (updateRetryCfg)
726         {
727             // Update the changed retry config to all subscriptions
728             for (const auto& it :
729                  EventServiceManager::getInstance().subscriptionsMap)
730             {
731                 Subscription& entry = *it.second;
732                 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval);
733             }
734         }
735     }
736 
737     void updateNoOfSubscribersCount()
738     {
739         size_t eventLogSubCount = 0;
740         size_t metricReportSubCount = 0;
741         for (const auto& it : subscriptionsMap)
742         {
743             std::shared_ptr<Subscription> entry = it.second;
744             if (entry->userSub.eventFormatType == eventFormatType)
745             {
746                 eventLogSubCount++;
747             }
748             else if (entry->userSub.eventFormatType == metricReportFormatType)
749             {
750                 metricReportSubCount++;
751             }
752         }
753 
754         noOfEventLogSubscribers = eventLogSubCount;
755         if (noOfMetricReportSubscribers != metricReportSubCount)
756         {
757             noOfMetricReportSubscribers = metricReportSubCount;
758             if (noOfMetricReportSubscribers != 0U)
759             {
760                 registerMetricReportSignal();
761             }
762             else
763             {
764                 unregisterMetricReportSignal();
765             }
766         }
767     }
768 
769     std::shared_ptr<Subscription> getSubscription(const std::string& id)
770     {
771         auto obj = subscriptionsMap.find(id);
772         if (obj == subscriptionsMap.end())
773         {
774             BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id);
775             return nullptr;
776         }
777         std::shared_ptr<Subscription> subValue = obj->second;
778         return subValue;
779     }
780 
781     std::string
782         addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue)
783     {
784         std::uniform_int_distribution<uint32_t> dist(0);
785         bmcweb::OpenSSLGenerator gen;
786 
787         std::string id;
788 
789         int retry = 3;
790         while (retry != 0)
791         {
792             id = std::to_string(dist(gen));
793             if (gen.error())
794             {
795                 retry = 0;
796                 break;
797             }
798             auto inserted = subscriptionsMap.insert(std::pair(id, subValue));
799             if (inserted.second)
800             {
801                 break;
802             }
803             --retry;
804         }
805 
806         if (retry <= 0)
807         {
808             BMCWEB_LOG_ERROR("Failed to generate random number");
809             return "";
810         }
811 
812         persistent_data::UserSubscription newSub(subValue->userSub);
813 
814         persistent_data::EventServiceStore::getInstance()
815             .subscriptionsConfigMap.emplace(newSub.id, newSub);
816 
817         updateNoOfSubscribersCount();
818 
819         if constexpr (!BMCWEB_REDFISH_DBUS_LOG)
820         {
821             if (redfishLogFilePosition != 0)
822             {
823                 cacheRedfishLogFile();
824             }
825         }
826         // Update retry configuration.
827         subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
828 
829         // Set Subscription ID for back trace
830         subValue->setSubscriptionId(id);
831 
832         return id;
833     }
834 
835     std::string
836         addSSESubscription(const std::shared_ptr<Subscription>& subValue,
837                            std::string_view lastEventId)
838     {
839         std::string id = addSubscriptionInternal(subValue);
840 
841         if (!lastEventId.empty())
842         {
843             BMCWEB_LOG_INFO("Attempting to find message for last id {}",
844                             lastEventId);
845             boost::circular_buffer<Event>::iterator lastEvent =
846                 std::find_if(messages.begin(), messages.end(),
847                              [&lastEventId](const Event& event) {
848                                  return event.id == lastEventId;
849                              });
850             // Can't find a matching ID
851             if (lastEvent == messages.end())
852             {
853                 nlohmann::json msg = messages::eventBufferExceeded();
854                 // If the buffer overloaded, send all messages.
855                 subValue->sendEventToSubscriber(msg);
856                 lastEvent = messages.begin();
857             }
858             else
859             {
860                 // Skip the last event the user already has
861                 lastEvent++;
862             }
863 
864             for (boost::circular_buffer<Event>::const_iterator event =
865                      lastEvent;
866                  lastEvent != messages.end(); lastEvent++)
867             {
868                 subValue->sendEventToSubscriber(event->message);
869             }
870         }
871         return id;
872     }
873 
874     std::string
875         addPushSubscription(const std::shared_ptr<Subscription>& subValue)
876     {
877         std::string id = addSubscriptionInternal(subValue);
878 
879         updateSubscriptionData();
880         return id;
881     }
882 
883     bool isSubscriptionExist(const std::string& id)
884     {
885         auto obj = subscriptionsMap.find(id);
886         return obj != subscriptionsMap.end();
887     }
888 
889     bool deleteSubscription(const std::string& id)
890     {
891         auto obj = subscriptionsMap.find(id);
892         if (obj == subscriptionsMap.end())
893         {
894             BMCWEB_LOG_WARNING("Could not find subscription with id {}", id);
895             return false;
896         }
897         subscriptionsMap.erase(obj);
898         auto& event = persistent_data::EventServiceStore::getInstance();
899         auto persistentObj = event.subscriptionsConfigMap.find(id);
900         if (persistentObj == event.subscriptionsConfigMap.end())
901         {
902             BMCWEB_LOG_ERROR("Subscription wasn't in persistent data");
903             return true;
904         }
905         persistent_data::EventServiceStore::getInstance()
906             .subscriptionsConfigMap.erase(persistentObj);
907         updateNoOfSubscribersCount();
908         updateSubscriptionData();
909 
910         return true;
911     }
912 
913     void deleteSseSubscription(const crow::sse_socket::Connection& thisConn)
914     {
915         for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();)
916         {
917             std::shared_ptr<Subscription> entry = it->second;
918             bool entryIsThisConn = entry->matchSseId(thisConn);
919             if (entryIsThisConn)
920             {
921                 persistent_data::EventServiceStore::getInstance()
922                     .subscriptionsConfigMap.erase(
923                         it->second->getSubscriptionId());
924                 it = subscriptionsMap.erase(it);
925                 return;
926             }
927             it++;
928         }
929     }
930 
931     size_t getNumberOfSubscriptions() const
932     {
933         return subscriptionsMap.size();
934     }
935 
936     size_t getNumberOfSSESubscriptions() const
937     {
938         auto size = std::ranges::count_if(
939             subscriptionsMap,
940             [](const std::pair<std::string, std::shared_ptr<Subscription>>&
941                    entry) {
942                 return (entry.second->userSub.subscriptionType ==
943                         subscriptionTypeSSE);
944             });
945         return static_cast<size_t>(size);
946     }
947 
948     std::vector<std::string> getAllIDs()
949     {
950         std::vector<std::string> idList;
951         for (const auto& it : subscriptionsMap)
952         {
953             idList.emplace_back(it.first);
954         }
955         return idList;
956     }
957 
958     bool sendTestEventLog()
959     {
960         for (const auto& it : subscriptionsMap)
961         {
962             std::shared_ptr<Subscription> entry = it.second;
963             if (!entry->sendTestEventLog())
964             {
965                 return false;
966             }
967         }
968         return true;
969     }
970 
971     void sendEvent(nlohmann::json::object_t eventMessage,
972                    std::string_view origin, std::string_view resourceType)
973     {
974         eventMessage["EventId"] = eventId;
975 
976         eventMessage["EventTimestamp"] =
977             redfish::time_utils::getDateTimeOffsetNow().first;
978         eventMessage["OriginOfCondition"] = origin;
979 
980         // MemberId is 0 : since we are sending one event record.
981         eventMessage["MemberId"] = "0";
982 
983         messages.push_back(Event(std::to_string(eventId), eventMessage));
984 
985         for (auto& it : subscriptionsMap)
986         {
987             std::shared_ptr<Subscription>& entry = it.second;
988             if (!eventMatchesFilter(entry->userSub, eventMessage, resourceType))
989             {
990                 BMCWEB_LOG_DEBUG("Filter didn't match");
991                 continue;
992             }
993 
994             nlohmann::json::array_t eventRecord;
995             eventRecord.emplace_back(eventMessage);
996 
997             nlohmann::json msgJson;
998 
999             msgJson["@odata.type"] = "#Event.v1_4_0.Event";
1000             msgJson["Name"] = "Event Log";
1001             msgJson["Id"] = eventId;
1002             msgJson["Events"] = std::move(eventRecord);
1003 
1004             std::string strMsg = msgJson.dump(
1005                 2, ' ', true, nlohmann::json::error_handler_t::replace);
1006             entry->sendEventToSubscriber(std::move(strMsg));
1007             eventId++; // increment the eventId
1008         }
1009     }
1010 
1011     void resetRedfishFilePosition()
1012     {
1013         // Control would be here when Redfish file is created.
1014         // Reset File Position as new file is created
1015         redfishLogFilePosition = 0;
1016     }
1017 
1018     void cacheRedfishLogFile()
1019     {
1020         // Open the redfish file and read till the last record.
1021 
1022         std::ifstream logStream(redfishEventLogFile);
1023         if (!logStream.good())
1024         {
1025             BMCWEB_LOG_ERROR(" Redfish log file open failed ");
1026             return;
1027         }
1028         std::string logEntry;
1029         while (std::getline(logStream, logEntry))
1030         {
1031             redfishLogFilePosition = logStream.tellg();
1032         }
1033     }
1034 
1035     void readEventLogsFromFile()
1036     {
1037         std::ifstream logStream(redfishEventLogFile);
1038         if (!logStream.good())
1039         {
1040             BMCWEB_LOG_ERROR(" Redfish log file open failed");
1041             return;
1042         }
1043 
1044         std::vector<EventLogObjectsType> eventRecords;
1045 
1046         std::string logEntry;
1047 
1048         BMCWEB_LOG_DEBUG("Redfish log file: seek to {}",
1049                          static_cast<int>(redfishLogFilePosition));
1050 
1051         // Get the read pointer to the next log to be read.
1052         logStream.seekg(redfishLogFilePosition);
1053 
1054         while (std::getline(logStream, logEntry))
1055         {
1056             BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry");
1057             // Update Pointer position
1058             redfishLogFilePosition = logStream.tellg();
1059 
1060             std::string idStr;
1061             if (!event_log::getUniqueEntryID(logEntry, idStr))
1062             {
1063                 BMCWEB_LOG_DEBUG(
1064                     "Redfish log file: could not get unique entry id for {}",
1065                     logEntry);
1066                 continue;
1067             }
1068 
1069             if (!serviceEnabled || noOfEventLogSubscribers == 0)
1070             {
1071                 // If Service is not enabled, no need to compute
1072                 // the remaining items below.
1073                 // But, Loop must continue to keep track of Timestamp
1074                 BMCWEB_LOG_DEBUG(
1075                     "Redfish log file: no subscribers / event service not enabled");
1076                 continue;
1077             }
1078 
1079             std::string timestamp;
1080             std::string messageID;
1081             std::vector<std::string> messageArgs;
1082             if (event_log::getEventLogParams(logEntry, timestamp, messageID,
1083                                              messageArgs) != 0)
1084             {
1085                 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}",
1086                                  logEntry);
1087                 continue;
1088             }
1089 
1090             eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs);
1091         }
1092 
1093         if (!serviceEnabled || noOfEventLogSubscribers == 0)
1094         {
1095             BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions.");
1096             return;
1097         }
1098 
1099         if (eventRecords.empty())
1100         {
1101             // No Records to send
1102             BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
1103             return;
1104         }
1105 
1106         for (const auto& it : subscriptionsMap)
1107         {
1108             std::shared_ptr<Subscription> entry = it.second;
1109             if (entry->userSub.eventFormatType == "Event")
1110             {
1111                 entry->filterAndSendEventLogs(eventRecords);
1112             }
1113         }
1114     }
1115 
1116     static void watchRedfishEventLogFile()
1117     {
1118         if (!inotifyConn)
1119         {
1120             BMCWEB_LOG_ERROR("inotify Connection is not present");
1121             return;
1122         }
1123 
1124         static std::array<char, 1024> readBuffer;
1125 
1126         inotifyConn->async_read_some(
1127             boost::asio::buffer(readBuffer),
1128             [&](const boost::system::error_code& ec,
1129                 const std::size_t& bytesTransferred) {
1130                 if (ec == boost::asio::error::operation_aborted)
1131                 {
1132                     BMCWEB_LOG_DEBUG("Inotify was canceled (shutdown?)");
1133                     return;
1134                 }
1135                 if (ec)
1136                 {
1137                     BMCWEB_LOG_ERROR("Callback Error: {}", ec.message());
1138                     return;
1139                 }
1140 
1141                 BMCWEB_LOG_DEBUG("reading {} via inotify", bytesTransferred);
1142 
1143                 std::size_t index = 0;
1144                 while ((index + iEventSize) <= bytesTransferred)
1145                 {
1146                     struct inotify_event event
1147                     {};
1148                     std::memcpy(&event, &readBuffer[index], iEventSize);
1149                     if (event.wd == dirWatchDesc)
1150                     {
1151                         if ((event.len == 0) ||
1152                             (index + iEventSize + event.len > bytesTransferred))
1153                         {
1154                             index += (iEventSize + event.len);
1155                             continue;
1156                         }
1157 
1158                         std::string fileName(&readBuffer[index + iEventSize]);
1159                         if (fileName != "redfish")
1160                         {
1161                             index += (iEventSize + event.len);
1162                             continue;
1163                         }
1164 
1165                         BMCWEB_LOG_DEBUG(
1166                             "Redfish log file created/deleted. event.name: {}",
1167                             fileName);
1168                         if (event.mask == IN_CREATE)
1169                         {
1170                             if (fileWatchDesc != -1)
1171                             {
1172                                 BMCWEB_LOG_DEBUG(
1173                                     "Remove and Add inotify watcher on "
1174                                     "redfish event log file");
1175                                 // Remove existing inotify watcher and add
1176                                 // with new redfish event log file.
1177                                 inotify_rm_watch(inotifyFd, fileWatchDesc);
1178                                 fileWatchDesc = -1;
1179                             }
1180 
1181                             fileWatchDesc = inotify_add_watch(
1182                                 inotifyFd, redfishEventLogFile, IN_MODIFY);
1183                             if (fileWatchDesc == -1)
1184                             {
1185                                 BMCWEB_LOG_ERROR("inotify_add_watch failed for "
1186                                                  "redfish log file.");
1187                                 return;
1188                             }
1189 
1190                             EventServiceManager::getInstance()
1191                                 .resetRedfishFilePosition();
1192                             EventServiceManager::getInstance()
1193                                 .readEventLogsFromFile();
1194                         }
1195                         else if ((event.mask == IN_DELETE) ||
1196                                  (event.mask == IN_MOVED_TO))
1197                         {
1198                             if (fileWatchDesc != -1)
1199                             {
1200                                 inotify_rm_watch(inotifyFd, fileWatchDesc);
1201                                 fileWatchDesc = -1;
1202                             }
1203                         }
1204                     }
1205                     else if (event.wd == fileWatchDesc)
1206                     {
1207                         if (event.mask == IN_MODIFY)
1208                         {
1209                             EventServiceManager::getInstance()
1210                                 .readEventLogsFromFile();
1211                         }
1212                     }
1213                     index += (iEventSize + event.len);
1214                 }
1215 
1216                 watchRedfishEventLogFile();
1217             });
1218     }
1219 
1220     static int startEventLogMonitor(boost::asio::io_context& ioc)
1221     {
1222         BMCWEB_LOG_DEBUG("starting Event Log Monitor");
1223 
1224         inotifyConn.emplace(ioc);
1225         inotifyFd = inotify_init1(IN_NONBLOCK);
1226         if (inotifyFd == -1)
1227         {
1228             BMCWEB_LOG_ERROR("inotify_init1 failed.");
1229             return -1;
1230         }
1231 
1232         // Add watch on directory to handle redfish event log file
1233         // create/delete.
1234         dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir,
1235                                          IN_CREATE | IN_MOVED_TO | IN_DELETE);
1236         if (dirWatchDesc == -1)
1237         {
1238             BMCWEB_LOG_ERROR(
1239                 "inotify_add_watch failed for event log directory.");
1240             return -1;
1241         }
1242 
1243         // Watch redfish event log file for modifications.
1244         fileWatchDesc =
1245             inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY);
1246         if (fileWatchDesc == -1)
1247         {
1248             BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file.");
1249             // Don't return error if file not exist.
1250             // Watch on directory will handle create/delete of file.
1251         }
1252 
1253         // monitor redfish event log file
1254         inotifyConn->assign(inotifyFd);
1255         watchRedfishEventLogFile();
1256 
1257         return 0;
1258     }
1259 
1260     static void stopEventLogMonitor()
1261     {
1262         inotifyConn.reset();
1263     }
1264 
1265     static void getReadingsForReport(sdbusplus::message_t& msg)
1266     {
1267         if (msg.is_method_error())
1268         {
1269             BMCWEB_LOG_ERROR("TelemetryMonitor Signal error");
1270             return;
1271         }
1272 
1273         sdbusplus::message::object_path path(msg.get_path());
1274         std::string id = path.filename();
1275         if (id.empty())
1276         {
1277             BMCWEB_LOG_ERROR("Failed to get Id from path");
1278             return;
1279         }
1280 
1281         std::string interface;
1282         dbus::utility::DBusPropertiesMap props;
1283         std::vector<std::string> invalidProps;
1284         msg.read(interface, props, invalidProps);
1285 
1286         auto found = std::ranges::find_if(props, [](const auto& x) {
1287             return x.first == "Readings";
1288         });
1289         if (found == props.end())
1290         {
1291             BMCWEB_LOG_INFO("Failed to get Readings from Report properties");
1292             return;
1293         }
1294 
1295         const telemetry::TimestampReadings* readings =
1296             std::get_if<telemetry::TimestampReadings>(&found->second);
1297         if (readings == nullptr)
1298         {
1299             BMCWEB_LOG_INFO("Failed to get Readings from Report properties");
1300             return;
1301         }
1302 
1303         for (const auto& it :
1304              EventServiceManager::getInstance().subscriptionsMap)
1305         {
1306             Subscription& entry = *it.second;
1307             if (entry.userSub.eventFormatType == metricReportFormatType)
1308             {
1309                 entry.filterAndSendReports(id, *readings);
1310             }
1311         }
1312     }
1313 
1314     void unregisterMetricReportSignal()
1315     {
1316         if (matchTelemetryMonitor)
1317         {
1318             BMCWEB_LOG_DEBUG("Metrics report signal - Unregister");
1319             matchTelemetryMonitor.reset();
1320             matchTelemetryMonitor = nullptr;
1321         }
1322     }
1323 
1324     void registerMetricReportSignal()
1325     {
1326         if (!serviceEnabled || matchTelemetryMonitor)
1327         {
1328             BMCWEB_LOG_DEBUG("Not registering metric report signal.");
1329             return;
1330         }
1331 
1332         BMCWEB_LOG_DEBUG("Metrics report signal - Register");
1333         std::string matchStr = "type='signal',member='PropertiesChanged',"
1334                                "interface='org.freedesktop.DBus.Properties',"
1335                                "arg0=xyz.openbmc_project.Telemetry.Report";
1336 
1337         matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>(
1338             *crow::connections::systemBus, matchStr, getReadingsForReport);
1339     }
1340 };
1341 
1342 } // namespace redfish
1343