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