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