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