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