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