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