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