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