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