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