xref: /openbmc/bmcweb/features/redfish/include/event_service_manager.hpp (revision 1bf712bcd6b8d06a5d412cdf03cdc05cb6d25901)
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 "node.hpp"
18 #include "registries.hpp"
19 #include "registries/base_message_registry.hpp"
20 #include "registries/openbmc_message_registry.hpp"
21 
22 #include <sys/inotify.h>
23 
24 #include <boost/container/flat_map.hpp>
25 #include <cstdlib>
26 #include <ctime>
27 #include <error_messages.hpp>
28 #include <fstream>
29 #include <http_client.hpp>
30 #include <memory>
31 #include <utils/json_utils.hpp>
32 #include <variant>
33 
34 namespace redfish
35 {
36 
37 using ReadingsObjType =
38     std::vector<std::tuple<std::string, std::string, double, std::string>>;
39 using EventServiceConfig = std::tuple<bool, uint32_t, uint32_t>;
40 
41 static constexpr const char* eventFormatType = "Event";
42 static constexpr const char* metricReportFormatType = "MetricReport";
43 
44 static constexpr const char* eventServiceFile =
45     "/var/lib/bmcweb/eventservice_config.json";
46 
47 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
48 constexpr const char* redfishEventLogFile = "/var/log/redfish";
49 constexpr const uint32_t inotifyFileAction = IN_MODIFY;
50 std::shared_ptr<boost::asio::posix::stream_descriptor> inotifyConn = nullptr;
51 
52 // <ID, timestamp, RedfishLogId, registryPrefix, MessageId, MessageArgs>
53 using EventLogObjectsType =
54     std::tuple<std::string, std::string, std::string, std::string, std::string,
55                boost::beast::span<std::string>>;
56 
57 namespace message_registries
58 {
59 static const Message*
60     getMsgFromRegistry(const std::string& messageKey,
61                        const boost::beast::span<const MessageEntry>& registry)
62 {
63     boost::beast::span<const MessageEntry>::const_iterator messageIt =
64         std::find_if(registry.cbegin(), registry.cend(),
65                      [&messageKey](const MessageEntry& messageEntry) {
66                          return !messageKey.compare(messageEntry.first);
67                      });
68     if (messageIt != registry.cend())
69     {
70         return &messageIt->second;
71     }
72 
73     return nullptr;
74 }
75 
76 static const Message* formatMessage(const std::string_view& messageID)
77 {
78     // Redfish MessageIds are in the form
79     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
80     // the right Message
81     std::vector<std::string> fields;
82     fields.reserve(4);
83     boost::split(fields, messageID, boost::is_any_of("."));
84     if (fields.size() != 4)
85     {
86         return nullptr;
87     }
88     std::string& registryName = fields[0];
89     std::string& messageKey = fields[3];
90 
91     // Find the right registry and check it for the MessageKey
92     if (std::string(base::header.registryPrefix) == registryName)
93     {
94         return getMsgFromRegistry(
95             messageKey, boost::beast::span<const MessageEntry>(base::registry));
96     }
97     if (std::string(openbmc::header.registryPrefix) == registryName)
98     {
99         return getMsgFromRegistry(
100             messageKey,
101             boost::beast::span<const MessageEntry>(openbmc::registry));
102     }
103     return nullptr;
104 }
105 } // namespace message_registries
106 
107 namespace event_log
108 {
109 bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
110                       const bool firstEntry = true)
111 {
112     static time_t prevTs = 0;
113     static int index = 0;
114     if (firstEntry)
115     {
116         prevTs = 0;
117     }
118 
119     // Get the entry timestamp
120     std::time_t curTs = 0;
121     std::tm timeStruct = {};
122     std::istringstream entryStream(logEntry);
123     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
124     {
125         curTs = std::mktime(&timeStruct);
126         if (curTs == -1)
127         {
128             return false;
129         }
130     }
131     // If the timestamp isn't unique, increment the index
132     index = (curTs == prevTs) ? index + 1 : 0;
133 
134     // Save the timestamp
135     prevTs = curTs;
136 
137     entryID = std::to_string(curTs);
138     if (index > 0)
139     {
140         entryID += "_" + std::to_string(index);
141     }
142     return true;
143 }
144 
145 int getEventLogParams(const std::string& logEntry, std::string& timestamp,
146                       std::string& messageID,
147                       boost::beast::span<std::string>& messageArgs)
148 {
149     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
150     // First get the Timestamp
151     size_t space = logEntry.find_first_of(" ");
152     if (space == std::string::npos)
153     {
154         return -EINVAL;
155     }
156     timestamp = logEntry.substr(0, space);
157     // Then get the log contents
158     size_t entryStart = logEntry.find_first_not_of(" ", space);
159     if (entryStart == std::string::npos)
160     {
161         return -EINVAL;
162     }
163     std::string_view entry(logEntry);
164     entry.remove_prefix(entryStart);
165     // Use split to separate the entry into its fields
166     std::vector<std::string> logEntryFields;
167     boost::split(logEntryFields, entry, boost::is_any_of(","),
168                  boost::token_compress_on);
169     // We need at least a MessageId to be valid
170     if (logEntryFields.size() < 1)
171     {
172         return -EINVAL;
173     }
174     messageID = logEntryFields[0];
175 
176     // Get the MessageArgs from the log if there are any
177     if (logEntryFields.size() > 1)
178     {
179         std::string& messageArgsStart = logEntryFields[1];
180         // If the first string is empty, assume there are no MessageArgs
181         std::size_t messageArgsSize = 0;
182         if (!messageArgsStart.empty())
183         {
184             messageArgsSize = logEntryFields.size() - 1;
185         }
186 
187         messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
188     }
189 
190     return 0;
191 }
192 
193 void getRegistryAndMessageKey(const std::string& messageID,
194                               std::string& registryName,
195                               std::string& messageKey)
196 {
197     // Redfish MessageIds are in the form
198     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
199     // the right Message
200     std::vector<std::string> fields;
201     fields.reserve(4);
202     boost::split(fields, messageID, boost::is_any_of("."));
203     if (fields.size() == 4)
204     {
205         registryName = fields[0];
206         messageKey = fields[3];
207     }
208 }
209 
210 int formatEventLogEntry(const std::string& logEntryID,
211                         const std::string& messageID,
212                         const boost::beast::span<std::string>& messageArgs,
213                         std::string timestamp, const std::string customText,
214                         nlohmann::json& logEntryJson)
215 {
216     // Get the Message from the MessageRegistry
217     const message_registries::Message* message =
218         message_registries::formatMessage(messageID);
219 
220     std::string msg;
221     std::string severity;
222     if (message != nullptr)
223     {
224         msg = message->message;
225         severity = message->severity;
226     }
227 
228     // Fill the MessageArgs into the Message
229     int i = 0;
230     for (const std::string& messageArg : messageArgs)
231     {
232         std::string argStr = "%" + std::to_string(++i);
233         size_t argPos = msg.find(argStr);
234         if (argPos != std::string::npos)
235         {
236             msg.replace(argPos, argStr.length(), messageArg);
237         }
238     }
239 
240     // Get the Created time from the timestamp. The log timestamp is in
241     // RFC3339 format which matches the Redfish format except for the
242     // fractional seconds between the '.' and the '+', so just remove them.
243     std::size_t dot = timestamp.find_first_of(".");
244     std::size_t plus = timestamp.find_first_of("+");
245     if (dot != std::string::npos && plus != std::string::npos)
246     {
247         timestamp.erase(dot, plus - dot);
248     }
249 
250     // Fill in the log entry with the gathered data
251     logEntryJson = {{"EventId", logEntryID},
252                     {"EventType", "Event"},
253                     {"Severity", std::move(severity)},
254                     {"Message", std::move(msg)},
255                     {"MessageId", std::move(messageID)},
256                     {"MessageArgs", std::move(messageArgs)},
257                     {"EventTimestamp", std::move(timestamp)},
258                     {"Context", customText}};
259     return 0;
260 }
261 
262 } // namespace event_log
263 #endif
264 
265 class Subscription
266 {
267   public:
268     std::string id;
269     std::string destinationUrl;
270     std::string protocol;
271     std::string retryPolicy;
272     std::string customText;
273     std::string eventFormatType;
274     std::string subscriptionType;
275     std::vector<std::string> registryMsgIds;
276     std::vector<std::string> registryPrefixes;
277     std::vector<nlohmann::json> httpHeaders; // key-value pair
278     std::vector<nlohmann::json> metricReportDefinitions;
279 
280     Subscription(const Subscription&) = delete;
281     Subscription& operator=(const Subscription&) = delete;
282     Subscription(Subscription&&) = delete;
283     Subscription& operator=(Subscription&&) = delete;
284 
285     Subscription(const std::string& inHost, const std::string& inPort,
286                  const std::string& inPath, const std::string& inUriProto) :
287         eventSeqNum(1),
288         host(inHost), port(inPort), path(inPath), uriProto(inUriProto)
289     {
290         conn = std::make_shared<crow::HttpClient>(
291             crow::connections::systemBus->get_io_context(), host, port, path);
292     }
293     ~Subscription()
294     {
295     }
296 
297     void sendEvent(const std::string& msg)
298     {
299         std::vector<std::pair<std::string, std::string>> reqHeaders;
300         for (const auto& header : httpHeaders)
301         {
302             for (const auto& item : header.items())
303             {
304                 std::string key = item.key();
305                 std::string val = item.value();
306                 reqHeaders.emplace_back(std::pair(key, val));
307             }
308         }
309         conn->setHeaders(reqHeaders);
310         conn->sendData(msg);
311     }
312 
313     void sendTestEventLog()
314     {
315         nlohmann::json logEntryArray;
316         logEntryArray.push_back({});
317         nlohmann::json& logEntryJson = logEntryArray.back();
318 
319         logEntryJson = {{"EventId", "TestID"},
320                         {"EventType", "Event"},
321                         {"Severity", "OK"},
322                         {"Message", "Generated test event"},
323                         {"MessageId", "OpenBMC.0.1.TestEventLog"},
324                         {"MessageArgs", nlohmann::json::array()},
325                         {"EventTimestamp", crow::utility::dateTimeNow()},
326                         {"Context", customText}};
327 
328         nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"},
329                               {"Id", std::to_string(eventSeqNum)},
330                               {"Name", "Event Log"},
331                               {"Events", logEntryArray}};
332 
333         this->sendEvent(msg.dump());
334         this->eventSeqNum++;
335     }
336 
337 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
338     void filterAndSendEventLogs(
339         const std::vector<EventLogObjectsType>& eventRecords)
340     {
341         nlohmann::json logEntryArray;
342         for (const EventLogObjectsType& logEntry : eventRecords)
343         {
344             const std::string& idStr = std::get<0>(logEntry);
345             const std::string& timestamp = std::get<1>(logEntry);
346             const std::string& messageID = std::get<2>(logEntry);
347             const std::string& registryName = std::get<3>(logEntry);
348             const std::string& messageKey = std::get<4>(logEntry);
349             const boost::beast::span<std::string>& messageArgs =
350                 std::get<5>(logEntry);
351 
352             // If registryPrefixes list is empty, don't filter events
353             // send everything.
354             if (registryPrefixes.size())
355             {
356                 auto obj = std::find(registryPrefixes.begin(),
357                                      registryPrefixes.end(), registryName);
358                 if (obj == registryPrefixes.end())
359                 {
360                     continue;
361                 }
362             }
363 
364             // If registryMsgIds list is empty, don't filter events
365             // send everything.
366             if (registryMsgIds.size())
367             {
368                 auto obj = std::find(registryMsgIds.begin(),
369                                      registryMsgIds.end(), messageKey);
370                 if (obj == registryMsgIds.end())
371                 {
372                     continue;
373                 }
374             }
375 
376             logEntryArray.push_back({});
377             nlohmann::json& bmcLogEntry = logEntryArray.back();
378             if (event_log::formatEventLogEntry(idStr, messageID, messageArgs,
379                                                timestamp, customText,
380                                                bmcLogEntry) != 0)
381             {
382                 BMCWEB_LOG_DEBUG << "Read eventLog entry failed";
383                 continue;
384             }
385         }
386 
387         if (logEntryArray.size() < 1)
388         {
389             BMCWEB_LOG_DEBUG << "No log entries available to be transferred.";
390             return;
391         }
392 
393         nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"},
394                               {"Id", std::to_string(eventSeqNum)},
395                               {"Name", "Event Log"},
396                               {"Events", logEntryArray}};
397 
398         this->sendEvent(msg.dump());
399         this->eventSeqNum++;
400     }
401 #endif
402 
403     void filterAndSendReports(const std::string& id,
404                               const std::string& readingsTs,
405                               const ReadingsObjType& readings)
406     {
407         std::string metricReportDef =
408             "/redfish/v1/TelemetryService/MetricReportDefinitions/" + id;
409 
410         // Empty list means no filter. Send everything.
411         if (metricReportDefinitions.size())
412         {
413             if (std::find(metricReportDefinitions.begin(),
414                           metricReportDefinitions.end(),
415                           metricReportDef) == metricReportDefinitions.end())
416             {
417                 return;
418             }
419         }
420 
421         nlohmann::json metricValuesArray = nlohmann::json::array();
422         for (const auto& it : readings)
423         {
424             metricValuesArray.push_back({});
425             nlohmann::json& entry = metricValuesArray.back();
426 
427             entry = {{"MetricId", std::get<0>(it)},
428                      {"MetricProperty", std::get<1>(it)},
429                      {"MetricValue", std::to_string(std::get<2>(it))},
430                      {"Timestamp", std::get<3>(it)}};
431         }
432 
433         nlohmann::json msg = {
434             {"@odata.id", "/redfish/v1/TelemetryService/MetricReports/" + id},
435             {"@odata.type", "#MetricReport.v1_3_0.MetricReport"},
436             {"Id", id},
437             {"Name", id},
438             {"Timestamp", readingsTs},
439             {"MetricReportDefinition", {{"@odata.id", metricReportDef}}},
440             {"MetricValues", metricValuesArray}};
441 
442         this->sendEvent(msg.dump());
443     }
444 
445   private:
446     uint64_t eventSeqNum;
447     std::string host;
448     std::string port;
449     std::string path;
450     std::string uriProto;
451     std::shared_ptr<crow::HttpClient> conn;
452 };
453 
454 static constexpr const bool defaultEnabledState = true;
455 static constexpr const uint32_t defaultRetryAttempts = 3;
456 static constexpr const uint32_t defaultRetryInterval = 30;
457 static constexpr const char* defaulEventFormatType = "Event";
458 static constexpr const char* defaulSubscriptionType = "RedfishEvent";
459 static constexpr const char* defaulRetryPolicy = "TerminateAfterRetries";
460 
461 class EventServiceManager
462 {
463   private:
464     bool serviceEnabled;
465     uint32_t retryAttempts;
466     uint32_t retryTimeoutInterval;
467 
468     EventServiceManager(const EventServiceManager&) = delete;
469     EventServiceManager& operator=(const EventServiceManager&) = delete;
470     EventServiceManager(EventServiceManager&&) = delete;
471     EventServiceManager& operator=(EventServiceManager&&) = delete;
472 
473     EventServiceManager() :
474         noOfEventLogSubscribers(0), noOfMetricReportSubscribers(0)
475     {
476         // Load config from persist store.
477         initConfig();
478     }
479 
480     std::string lastEventTStr;
481     size_t noOfEventLogSubscribers;
482     size_t noOfMetricReportSubscribers;
483     std::shared_ptr<sdbusplus::bus::match::match> matchTelemetryMonitor;
484     boost::container::flat_map<std::string, std::shared_ptr<Subscription>>
485         subscriptionsMap;
486 
487   public:
488     static EventServiceManager& getInstance()
489     {
490         static EventServiceManager handler;
491         return handler;
492     }
493 
494     void loadDefaultConfig()
495     {
496         serviceEnabled = defaultEnabledState;
497         retryAttempts = defaultRetryAttempts;
498         retryTimeoutInterval = defaultRetryInterval;
499     }
500 
501     void initConfig()
502     {
503         std::ifstream eventConfigFile(eventServiceFile);
504         if (!eventConfigFile.good())
505         {
506             BMCWEB_LOG_DEBUG << "EventService config not exist";
507             loadDefaultConfig();
508             return;
509         }
510         auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false);
511         if (jsonData.is_discarded())
512         {
513             BMCWEB_LOG_ERROR << "EventService config parse error.";
514             loadDefaultConfig();
515             return;
516         }
517 
518         nlohmann::json jsonConfig;
519         if (json_util::getValueFromJsonObject(jsonData, "Configuration",
520                                               jsonConfig))
521         {
522             if (!json_util::getValueFromJsonObject(jsonConfig, "ServiceEnabled",
523                                                    serviceEnabled))
524             {
525                 serviceEnabled = defaultEnabledState;
526             }
527             if (!json_util::getValueFromJsonObject(
528                     jsonConfig, "DeliveryRetryAttempts", retryAttempts))
529             {
530                 retryAttempts = defaultRetryAttempts;
531             }
532             if (!json_util::getValueFromJsonObject(
533                     jsonConfig, "DeliveryRetryIntervalSeconds",
534                     retryTimeoutInterval))
535             {
536                 retryTimeoutInterval = defaultRetryInterval;
537             }
538         }
539         else
540         {
541             loadDefaultConfig();
542         }
543 
544         nlohmann::json subscriptionsList;
545         if (!json_util::getValueFromJsonObject(jsonData, "Subscriptions",
546                                                subscriptionsList))
547         {
548             BMCWEB_LOG_DEBUG << "EventService: Subscriptions not exist.";
549             return;
550         }
551 
552         for (nlohmann::json& jsonObj : subscriptionsList)
553         {
554             std::string protocol;
555             if (!json_util::getValueFromJsonObject(jsonObj, "Protocol",
556                                                    protocol))
557             {
558                 BMCWEB_LOG_DEBUG << "Invalid subscription Protocol exist.";
559                 continue;
560             }
561             std::string destination;
562             if (!json_util::getValueFromJsonObject(jsonObj, "Destination",
563                                                    destination))
564             {
565                 BMCWEB_LOG_DEBUG << "Invalid subscription destination exist.";
566                 continue;
567             }
568             std::string host;
569             std::string urlProto;
570             std::string port;
571             std::string path;
572             bool status =
573                 validateAndSplitUrl(destination, urlProto, host, port, path);
574 
575             if (!status)
576             {
577                 BMCWEB_LOG_ERROR
578                     << "Failed to validate and split destination url";
579                 continue;
580             }
581             std::shared_ptr<Subscription> subValue =
582                 std::make_shared<Subscription>(host, port, path, urlProto);
583 
584             subValue->destinationUrl = destination;
585             subValue->protocol = protocol;
586             if (!json_util::getValueFromJsonObject(
587                     jsonObj, "DeliveryRetryPolicy", subValue->retryPolicy))
588             {
589                 subValue->retryPolicy = defaulRetryPolicy;
590             }
591             if (!json_util::getValueFromJsonObject(jsonObj, "EventFormatType",
592                                                    subValue->eventFormatType))
593             {
594                 subValue->eventFormatType = defaulEventFormatType;
595             }
596             if (!json_util::getValueFromJsonObject(jsonObj, "SubscriptionType",
597                                                    subValue->subscriptionType))
598             {
599                 subValue->subscriptionType = defaulSubscriptionType;
600             }
601 
602             json_util::getValueFromJsonObject(jsonObj, "Context",
603                                               subValue->customText);
604             json_util::getValueFromJsonObject(jsonObj, "MessageIds",
605                                               subValue->registryMsgIds);
606             json_util::getValueFromJsonObject(jsonObj, "RegistryPrefixes",
607                                               subValue->registryPrefixes);
608             json_util::getValueFromJsonObject(jsonObj, "HttpHeaders",
609                                               subValue->httpHeaders);
610             json_util::getValueFromJsonObject(
611                 jsonObj, "MetricReportDefinitions",
612                 subValue->metricReportDefinitions);
613 
614             std::string id = addSubscription(subValue, false);
615             if (id.empty())
616             {
617                 BMCWEB_LOG_ERROR << "Failed to add subscription";
618             }
619         }
620         return;
621     }
622 
623     void updateSubscriptionData()
624     {
625         // Persist the config and subscription data.
626         nlohmann::json jsonData;
627 
628         nlohmann::json& configObj = jsonData["Configuration"];
629         configObj["ServiceEnabled"] = serviceEnabled;
630         configObj["DeliveryRetryAttempts"] = retryAttempts;
631         configObj["DeliveryRetryIntervalSeconds"] = retryTimeoutInterval;
632 
633         nlohmann::json& subListArray = jsonData["Subscriptions"];
634         subListArray = nlohmann::json::array();
635 
636         for (const auto& it : subscriptionsMap)
637         {
638             nlohmann::json entry;
639             std::shared_ptr<Subscription> subValue = it.second;
640 
641             entry["Context"] = subValue->customText;
642             entry["DeliveryRetryPolicy"] = subValue->retryPolicy;
643             entry["Destination"] = subValue->destinationUrl;
644             entry["EventFormatType"] = subValue->eventFormatType;
645             entry["HttpHeaders"] = subValue->httpHeaders;
646             entry["MessageIds"] = subValue->registryMsgIds;
647             entry["Protocol"] = subValue->protocol;
648             entry["RegistryPrefixes"] = subValue->registryPrefixes;
649             entry["SubscriptionType"] = subValue->subscriptionType;
650             entry["MetricReportDefinitions"] =
651                 subValue->metricReportDefinitions;
652 
653             subListArray.push_back(entry);
654         }
655 
656         const std::string tmpFile(std::string(eventServiceFile) + "_tmp");
657         std::ofstream ofs(tmpFile, std::ios::out);
658         const auto& writeData = jsonData.dump();
659         ofs << writeData;
660         ofs.close();
661 
662         BMCWEB_LOG_DEBUG << "EventService config updated to file.";
663         if (std::rename(tmpFile.c_str(), eventServiceFile) != 0)
664         {
665             BMCWEB_LOG_ERROR << "Error in renaming temporary file: "
666                              << tmpFile.c_str();
667         }
668     }
669 
670     EventServiceConfig getEventServiceConfig()
671     {
672         return {serviceEnabled, retryAttempts, retryTimeoutInterval};
673     }
674 
675     void setEventServiceConfig(const EventServiceConfig& cfg)
676     {
677         bool updateConfig = false;
678 
679         if (serviceEnabled != std::get<0>(cfg))
680         {
681             serviceEnabled = std::get<0>(cfg);
682             if (serviceEnabled && noOfMetricReportSubscribers)
683             {
684                 registerMetricReportSignal();
685             }
686             else
687             {
688                 unregisterMetricReportSignal();
689             }
690             updateConfig = true;
691         }
692 
693         if (retryAttempts != std::get<1>(cfg))
694         {
695             retryAttempts = std::get<1>(cfg);
696             updateConfig = true;
697         }
698 
699         if (retryTimeoutInterval != std::get<2>(cfg))
700         {
701             retryTimeoutInterval = std::get<2>(cfg);
702             updateConfig = true;
703         }
704 
705         if (updateConfig)
706         {
707             updateSubscriptionData();
708         }
709     }
710 
711     void updateNoOfSubscribersCount()
712     {
713         size_t eventLogSubCount = 0;
714         size_t metricReportSubCount = 0;
715         for (const auto& it : subscriptionsMap)
716         {
717             std::shared_ptr<Subscription> entry = it.second;
718             if (entry->eventFormatType == eventFormatType)
719             {
720                 eventLogSubCount++;
721             }
722             else if (entry->eventFormatType == metricReportFormatType)
723             {
724                 metricReportSubCount++;
725             }
726         }
727 
728         noOfEventLogSubscribers = eventLogSubCount;
729         if (noOfMetricReportSubscribers != metricReportSubCount)
730         {
731             noOfMetricReportSubscribers = metricReportSubCount;
732             if (noOfMetricReportSubscribers)
733             {
734                 registerMetricReportSignal();
735             }
736             else
737             {
738                 unregisterMetricReportSignal();
739             }
740         }
741     }
742 
743     std::shared_ptr<Subscription> getSubscription(const std::string& id)
744     {
745         auto obj = subscriptionsMap.find(id);
746         if (obj == subscriptionsMap.end())
747         {
748             BMCWEB_LOG_ERROR << "No subscription exist with ID:" << id;
749             return nullptr;
750         }
751         std::shared_ptr<Subscription> subValue = obj->second;
752         return subValue;
753     }
754 
755     std::string addSubscription(const std::shared_ptr<Subscription> subValue,
756                                 const bool updateFile = true)
757     {
758         std::srand(static_cast<uint32_t>(std::time(0)));
759         std::string id;
760 
761         int retry = 3;
762         while (retry)
763         {
764             id = std::to_string(std::rand());
765             auto inserted = subscriptionsMap.insert(std::pair(id, subValue));
766             if (inserted.second)
767             {
768                 break;
769             }
770             --retry;
771         };
772 
773         if (retry <= 0)
774         {
775             BMCWEB_LOG_ERROR << "Failed to generate random number";
776             return std::string("");
777         }
778 
779         updateNoOfSubscribersCount();
780 
781         if (updateFile)
782         {
783             updateSubscriptionData();
784         }
785 
786 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
787         if (lastEventTStr.empty())
788         {
789             cacheLastEventTimestamp();
790         }
791 #endif
792         return id;
793     }
794 
795     bool isSubscriptionExist(const std::string& id)
796     {
797         auto obj = subscriptionsMap.find(id);
798         if (obj == subscriptionsMap.end())
799         {
800             return false;
801         }
802         return true;
803     }
804 
805     void deleteSubscription(const std::string& id)
806     {
807         auto obj = subscriptionsMap.find(id);
808         if (obj != subscriptionsMap.end())
809         {
810             subscriptionsMap.erase(obj);
811             updateNoOfSubscribersCount();
812             updateSubscriptionData();
813         }
814     }
815 
816     size_t getNumberOfSubscriptions()
817     {
818         return subscriptionsMap.size();
819     }
820 
821     std::vector<std::string> getAllIDs()
822     {
823         std::vector<std::string> idList;
824         for (const auto& it : subscriptionsMap)
825         {
826             idList.emplace_back(it.first);
827         }
828         return idList;
829     }
830 
831     bool isDestinationExist(const std::string& destUrl)
832     {
833         for (const auto& it : subscriptionsMap)
834         {
835             std::shared_ptr<Subscription> entry = it.second;
836             if (entry->destinationUrl == destUrl)
837             {
838                 BMCWEB_LOG_ERROR << "Destination exist already" << destUrl;
839                 return true;
840             }
841         }
842         return false;
843     }
844 
845     void sendTestEventLog()
846     {
847         for (const auto& it : this->subscriptionsMap)
848         {
849             std::shared_ptr<Subscription> entry = it.second;
850             entry->sendTestEventLog();
851         }
852     }
853 
854 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
855     void cacheLastEventTimestamp()
856     {
857         std::ifstream logStream(redfishEventLogFile);
858         if (!logStream.good())
859         {
860             BMCWEB_LOG_ERROR << " Redfish log file open failed \n";
861             return;
862         }
863         std::string logEntry;
864         while (std::getline(logStream, logEntry))
865         {
866             size_t space = logEntry.find_first_of(" ");
867             if (space == std::string::npos)
868             {
869                 // Shouldn't enter here but lets skip it.
870                 BMCWEB_LOG_DEBUG << "Invalid log entry found.";
871                 continue;
872             }
873             lastEventTStr = logEntry.substr(0, space);
874         }
875         BMCWEB_LOG_DEBUG << "Last Event time stamp set: " << lastEventTStr;
876     }
877 
878     void readEventLogsFromFile()
879     {
880         if (!serviceEnabled || !noOfEventLogSubscribers)
881         {
882             BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions.";
883             return;
884         }
885         std::ifstream logStream(redfishEventLogFile);
886         if (!logStream.good())
887         {
888             BMCWEB_LOG_ERROR << " Redfish log file open failed";
889             return;
890         }
891 
892         std::vector<EventLogObjectsType> eventRecords;
893 
894         bool startLogCollection = false;
895         bool firstEntry = true;
896 
897         std::string logEntry;
898         while (std::getline(logStream, logEntry))
899         {
900             if (!startLogCollection)
901             {
902                 if (boost::starts_with(logEntry, lastEventTStr))
903                 {
904                     startLogCollection = true;
905                 }
906                 continue;
907             }
908 
909             std::string idStr;
910             if (!event_log::getUniqueEntryID(logEntry, idStr, firstEntry))
911             {
912                 continue;
913             }
914             firstEntry = false;
915 
916             std::string timestamp;
917             std::string messageID;
918             boost::beast::span<std::string> messageArgs;
919             if (event_log::getEventLogParams(logEntry, timestamp, messageID,
920                                              messageArgs) != 0)
921             {
922                 BMCWEB_LOG_DEBUG << "Read eventLog entry params failed";
923                 continue;
924             }
925 
926             std::string registryName;
927             std::string messageKey;
928             event_log::getRegistryAndMessageKey(messageID, registryName,
929                                                 messageKey);
930             if (registryName.empty() || messageKey.empty())
931             {
932                 continue;
933             }
934 
935             lastEventTStr = timestamp;
936             eventRecords.emplace_back(idStr, timestamp, messageID, registryName,
937                                       messageKey, messageArgs);
938         }
939 
940         for (const auto& it : this->subscriptionsMap)
941         {
942             std::shared_ptr<Subscription> entry = it.second;
943             if (entry->eventFormatType == "Event")
944             {
945                 entry->filterAndSendEventLogs(eventRecords);
946             }
947         }
948     }
949 
950     static void watchRedfishEventLogFile()
951     {
952         if (inotifyConn == nullptr)
953         {
954             return;
955         }
956 
957         static std::array<char, 1024> readBuffer;
958 
959         inotifyConn->async_read_some(
960             boost::asio::buffer(readBuffer),
961             [&](const boost::system::error_code& ec,
962                 const std::size_t& bytesTransferred) {
963                 if (ec)
964                 {
965                     BMCWEB_LOG_ERROR << "Callback Error: " << ec.message();
966                     return;
967                 }
968                 std::size_t index = 0;
969                 while ((index + sizeof(inotify_event)) <= bytesTransferred)
970                 {
971                     struct inotify_event event;
972                     std::memcpy(&event, &readBuffer[index],
973                                 sizeof(inotify_event));
974                     if (event.mask == inotifyFileAction)
975                     {
976                         EventServiceManager::getInstance()
977                             .readEventLogsFromFile();
978                     }
979                     index += (sizeof(inotify_event) + event.len);
980                 }
981 
982                 watchRedfishEventLogFile();
983             });
984     }
985 
986     static int startEventLogMonitor(boost::asio::io_service& ioc)
987     {
988         inotifyConn =
989             std::make_shared<boost::asio::posix::stream_descriptor>(ioc);
990         int fd = inotify_init1(IN_NONBLOCK);
991         if (fd == -1)
992         {
993             BMCWEB_LOG_ERROR << "inotify_init1 failed.";
994             return -1;
995         }
996         auto wd = inotify_add_watch(fd, redfishEventLogFile, inotifyFileAction);
997         if (wd == -1)
998         {
999             BMCWEB_LOG_ERROR
1000                 << "inotify_add_watch failed for redfish log file.";
1001             return -1;
1002         }
1003 
1004         // monitor redfish event log file
1005         inotifyConn->assign(fd);
1006         watchRedfishEventLogFile();
1007 
1008         return 0;
1009     }
1010 
1011 #endif
1012 
1013     void getMetricReading(const std::string& service,
1014                           const std::string& objPath, const std::string& intf)
1015     {
1016         std::size_t found = objPath.find_last_of("/");
1017         if (found == std::string::npos)
1018         {
1019             BMCWEB_LOG_DEBUG << "Invalid objPath received";
1020             return;
1021         }
1022 
1023         std::string idStr = objPath.substr(found + 1);
1024         if (idStr.empty())
1025         {
1026             BMCWEB_LOG_DEBUG << "Invalid ID in objPath";
1027             return;
1028         }
1029 
1030         crow::connections::systemBus->async_method_call(
1031             [idStr{std::move(idStr)}](
1032                 const boost::system::error_code ec,
1033                 boost::container::flat_map<
1034                     std::string, std::variant<std::string, ReadingsObjType>>&
1035                     resp) {
1036                 if (ec)
1037                 {
1038                     BMCWEB_LOG_DEBUG
1039                         << "D-Bus call failed to GetAll metric readings.";
1040                     return;
1041                 }
1042 
1043                 const std::string* timestampPtr =
1044                     std::get_if<std::string>(&resp["Timestamp"]);
1045                 if (!timestampPtr)
1046                 {
1047                     BMCWEB_LOG_DEBUG << "Failed to Get timestamp.";
1048                     return;
1049                 }
1050 
1051                 ReadingsObjType* readingsPtr =
1052                     std::get_if<ReadingsObjType>(&resp["Readings"]);
1053                 if (!readingsPtr)
1054                 {
1055                     BMCWEB_LOG_DEBUG << "Failed to Get Readings property.";
1056                     return;
1057                 }
1058 
1059                 if (!readingsPtr->size())
1060                 {
1061                     BMCWEB_LOG_DEBUG << "No metrics report to be transferred";
1062                     return;
1063                 }
1064 
1065                 for (const auto& it :
1066                      EventServiceManager::getInstance().subscriptionsMap)
1067                 {
1068                     std::shared_ptr<Subscription> entry = it.second;
1069                     if (entry->eventFormatType == metricReportFormatType)
1070                     {
1071                         entry->filterAndSendReports(idStr, *timestampPtr,
1072                                                     *readingsPtr);
1073                     }
1074                 }
1075             },
1076             service, objPath, "org.freedesktop.DBus.Properties", "GetAll",
1077             intf);
1078     }
1079 
1080     void unregisterMetricReportSignal()
1081     {
1082         if (matchTelemetryMonitor)
1083         {
1084             BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister";
1085             matchTelemetryMonitor.reset();
1086             matchTelemetryMonitor = nullptr;
1087         }
1088     }
1089 
1090     void registerMetricReportSignal()
1091     {
1092         if (!serviceEnabled || matchTelemetryMonitor)
1093         {
1094             BMCWEB_LOG_DEBUG << "Not registering metric report signal.";
1095             return;
1096         }
1097 
1098         BMCWEB_LOG_DEBUG << "Metrics report signal - Register";
1099         std::string matchStr(
1100             "type='signal',member='ReportUpdate', "
1101             "interface='xyz.openbmc_project.MonitoringService.Report'");
1102 
1103         matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>(
1104             *crow::connections::systemBus, matchStr,
1105             [this](sdbusplus::message::message& msg) {
1106                 if (msg.is_method_error())
1107                 {
1108                     BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error";
1109                     return;
1110                 }
1111 
1112                 std::string service = msg.get_sender();
1113                 std::string objPath = msg.get_path();
1114                 std::string intf = msg.get_interface();
1115                 getMetricReading(service, objPath, intf);
1116             });
1117     }
1118 
1119     bool validateAndSplitUrl(const std::string& destUrl, std::string& urlProto,
1120                              std::string& host, std::string& port,
1121                              std::string& path)
1122     {
1123         // Validate URL using regex expression
1124         // Format: <protocol>://<host>:<port>/<path>
1125         // protocol: http/https
1126         const std::regex urlRegex(
1127             "(http|https)://([^/\\x20\\x3f\\x23\\x3a]+):?([0-9]*)(/"
1128             "([^\\x20\\x23\\x3f]*\\x3f?([^\\x20\\x23\\x3f])*)?)");
1129         std::cmatch match;
1130         if (!std::regex_match(destUrl.c_str(), match, urlRegex))
1131         {
1132             BMCWEB_LOG_INFO << "Dest. url did not match ";
1133             return false;
1134         }
1135 
1136         urlProto = std::string(match[1].first, match[1].second);
1137         if (urlProto == "http")
1138         {
1139 #ifndef BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING
1140             return false;
1141 #endif
1142         }
1143 
1144         host = std::string(match[2].first, match[2].second);
1145         port = std::string(match[3].first, match[3].second);
1146         path = std::string(match[4].first, match[4].second);
1147         if (port.empty())
1148         {
1149             if (urlProto == "http")
1150             {
1151                 port = "80";
1152             }
1153             else
1154             {
1155                 port = "443";
1156             }
1157         }
1158         if (path.empty())
1159         {
1160             path = "/";
1161         }
1162         return true;
1163     }
1164 }; // namespace redfish
1165 
1166 } // namespace redfish
1167