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