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