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 #include "subscription.hpp"
17 
18 #include "dbus_singleton.hpp"
19 #include "event_log.hpp"
20 #include "event_logs_object_type.hpp"
21 #include "event_matches_filter.hpp"
22 #include "event_service_store.hpp"
23 #include "filter_expr_executor.hpp"
24 #include "generated/enums/log_entry.hpp"
25 #include "heartbeat_messages.hpp"
26 #include "http_client.hpp"
27 #include "http_response.hpp"
28 #include "logging.hpp"
29 #include "metric_report.hpp"
30 #include "server_sent_event.hpp"
31 #include "ssl_key_handler.hpp"
32 #include "utils/time_utils.hpp"
33 
34 #include <boost/asio/error.hpp>
35 #include <boost/asio/io_context.hpp>
36 #include <boost/asio/steady_timer.hpp>
37 #include <boost/beast/http/verb.hpp>
38 #include <boost/system/errc.hpp>
39 #include <boost/url/format.hpp>
40 #include <boost/url/url_view_base.hpp>
41 #include <nlohmann/json.hpp>
42 
43 #include <algorithm>
44 #include <chrono>
45 #include <cstdint>
46 #include <cstdlib>
47 #include <ctime>
48 #include <format>
49 #include <functional>
50 #include <memory>
51 #include <span>
52 #include <string>
53 #include <string_view>
54 #include <utility>
55 #include <vector>
56 
57 namespace redfish
58 {
59 
60 Subscription::Subscription(
61     std::shared_ptr<persistent_data::UserSubscription> userSubIn,
62     const boost::urls::url_view_base& url, boost::asio::io_context& ioc) :
63     userSub{std::move(userSubIn)},
64     policy(std::make_shared<crow::ConnectionPolicy>()), hbTimer(ioc)
65 {
66     userSub->destinationUrl = url;
67     client.emplace(ioc, policy);
68     // Subscription constructor
69     policy->invalidResp = retryRespHandler;
70 }
71 
72 Subscription::Subscription(crow::sse_socket::Connection& connIn) :
73     userSub{std::make_shared<persistent_data::UserSubscription>()},
74     sseConn(&connIn), hbTimer(crow::connections::systemBus->get_io_context())
75 {}
76 
77 // callback for subscription sendData
78 void Subscription::resHandler(const std::shared_ptr<Subscription>& /*unused*/,
79                               const crow::Response& res)
80 {
81     BMCWEB_LOG_DEBUG("Response handled with return code: {}", res.resultInt());
82 
83     if (!client)
84     {
85         BMCWEB_LOG_ERROR(
86             "Http client wasn't filled but http client callback was called.");
87         return;
88     }
89 
90     if (userSub->retryPolicy != "TerminateAfterRetries")
91     {
92         return;
93     }
94     if (client->isTerminated())
95     {
96         hbTimer.cancel();
97         if (deleter)
98         {
99             BMCWEB_LOG_INFO("Subscription {} is deleted after MaxRetryAttempts",
100                             userSub->id);
101             deleter();
102         }
103     }
104 }
105 
106 void Subscription::sendHeartbeatEvent()
107 {
108     // send the heartbeat message
109     nlohmann::json eventMessage = messages::redfishServiceFunctional();
110 
111     std::string heartEventId = std::to_string(eventSeqNum);
112     eventMessage["EventId"] = heartEventId;
113     eventMessage["EventTimestamp"] = time_utils::getDateTimeOffsetNow().first;
114     eventMessage["OriginOfCondition"] =
115         std::format("/redfish/v1/EventService/Subscriptions/{}", userSub->id);
116     eventMessage["MemberId"] = "0";
117 
118     nlohmann::json::array_t eventRecord;
119     eventRecord.emplace_back(std::move(eventMessage));
120 
121     nlohmann::json msgJson;
122     msgJson["@odata.type"] = "#Event.v1_4_0.Event";
123     msgJson["Name"] = "Heartbeat";
124     msgJson["Id"] = heartEventId;
125     msgJson["Events"] = std::move(eventRecord);
126 
127     std::string strMsg =
128         msgJson.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
129     sendEventToSubscriber(std::move(strMsg));
130     eventSeqNum++;
131 }
132 
133 void Subscription::scheduleNextHeartbeatEvent()
134 {
135     hbTimer.expires_after(std::chrono::minutes(userSub->hbIntervalMinutes));
136     hbTimer.async_wait(
137         std::bind_front(&Subscription::onHbTimeout, this, weak_from_this()));
138 }
139 
140 void Subscription::heartbeatParametersChanged()
141 {
142     hbTimer.cancel();
143 
144     if (userSub->sendHeartbeat)
145     {
146         scheduleNextHeartbeatEvent();
147     }
148 }
149 
150 void Subscription::onHbTimeout(const std::weak_ptr<Subscription>& weakSelf,
151                                const boost::system::error_code& ec)
152 {
153     if (ec == boost::asio::error::operation_aborted)
154     {
155         BMCWEB_LOG_DEBUG("heartbeat timer async_wait is aborted");
156         return;
157     }
158     if (ec == boost::system::errc::operation_canceled)
159     {
160         BMCWEB_LOG_DEBUG("heartbeat timer async_wait canceled");
161         return;
162     }
163     if (ec)
164     {
165         BMCWEB_LOG_CRITICAL("heartbeat timer async_wait failed: {}", ec);
166         return;
167     }
168 
169     std::shared_ptr<Subscription> self = weakSelf.lock();
170     if (!self)
171     {
172         BMCWEB_LOG_CRITICAL("onHbTimeout failed on Subscription");
173         return;
174     }
175 
176     // Timer expired.
177     sendHeartbeatEvent();
178 
179     // reschedule heartbeat timer
180     scheduleNextHeartbeatEvent();
181 }
182 
183 bool Subscription::sendEventToSubscriber(std::string&& msg)
184 {
185     persistent_data::EventServiceConfig eventServiceConfig =
186         persistent_data::EventServiceStore::getInstance()
187             .getEventServiceConfig();
188     if (!eventServiceConfig.enabled)
189     {
190         return false;
191     }
192 
193     if (client)
194     {
195         client->sendDataWithCallback(
196             std::move(msg), userSub->destinationUrl,
197             static_cast<ensuressl::VerifyCertificate>(
198                 userSub->verifyCertificate),
199             userSub->httpHeaders, boost::beast::http::verb::post,
200             std::bind_front(&Subscription::resHandler, this,
201                             shared_from_this()));
202         return true;
203     }
204 
205     if (sseConn != nullptr)
206     {
207         eventSeqNum++;
208         sseConn->sendSseEvent(std::to_string(eventSeqNum), msg);
209     }
210     return true;
211 }
212 
213 bool Subscription::sendTestEventLog()
214 {
215     nlohmann::json::array_t logEntryArray;
216     nlohmann::json& logEntryJson = logEntryArray.emplace_back();
217 
218     logEntryJson["EventId"] = "TestID";
219     logEntryJson["Severity"] = log_entry::EventSeverity::OK;
220     logEntryJson["Message"] = "Generated test event";
221     logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog";
222     // MemberId is 0 : since we are sending one event record.
223     logEntryJson["MemberId"] = "0";
224     logEntryJson["MessageArgs"] = nlohmann::json::array();
225     logEntryJson["EventTimestamp"] =
226         redfish::time_utils::getDateTimeOffsetNow().first;
227     logEntryJson["Context"] = userSub->customText;
228 
229     nlohmann::json msg;
230     msg["@odata.type"] = "#Event.v1_4_0.Event";
231     msg["Id"] = std::to_string(eventSeqNum);
232     msg["Name"] = "Event Log";
233     msg["Events"] = logEntryArray;
234 
235     std::string strMsg =
236         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
237     return sendEventToSubscriber(std::move(strMsg));
238 }
239 
240 void Subscription::filterAndSendEventLogs(
241     const std::vector<EventLogObjectsType>& eventRecords)
242 {
243     nlohmann::json::array_t logEntryArray;
244     for (const EventLogObjectsType& logEntry : eventRecords)
245     {
246         std::vector<std::string_view> messageArgsView(
247             logEntry.messageArgs.begin(), logEntry.messageArgs.end());
248 
249         nlohmann::json::object_t bmcLogEntry;
250         if (event_log::formatEventLogEntry(
251                 logEntry.id, logEntry.messageId, messageArgsView,
252                 logEntry.timestamp, userSub->customText, bmcLogEntry) != 0)
253         {
254             BMCWEB_LOG_DEBUG("Read eventLog entry failed");
255             continue;
256         }
257 
258         if (!eventMatchesFilter(*userSub, bmcLogEntry, ""))
259         {
260             BMCWEB_LOG_DEBUG("Event {} did not match the filter",
261                              nlohmann::json(bmcLogEntry).dump());
262             continue;
263         }
264 
265         if (filter)
266         {
267             if (!memberMatches(bmcLogEntry, *filter))
268             {
269                 BMCWEB_LOG_DEBUG("Filter didn't match");
270                 continue;
271             }
272         }
273 
274         logEntryArray.emplace_back(std::move(bmcLogEntry));
275     }
276 
277     if (logEntryArray.empty())
278     {
279         BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
280         return;
281     }
282 
283     nlohmann::json msg;
284     msg["@odata.type"] = "#Event.v1_4_0.Event";
285     msg["Id"] = std::to_string(eventSeqNum);
286     msg["Name"] = "Event Log";
287     msg["Events"] = std::move(logEntryArray);
288     std::string strMsg =
289         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
290     sendEventToSubscriber(std::move(strMsg));
291     eventSeqNum++;
292 }
293 
294 void Subscription::filterAndSendReports(const std::string& reportId,
295                                         const telemetry::TimestampReadings& var)
296 {
297     boost::urls::url mrdUri = boost::urls::format(
298         "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", reportId);
299 
300     // Empty list means no filter. Send everything.
301     if (!userSub->metricReportDefinitions.empty())
302     {
303         if (std::ranges::find(userSub->metricReportDefinitions,
304                               mrdUri.buffer()) ==
305             userSub->metricReportDefinitions.end())
306         {
307             return;
308         }
309     }
310 
311     nlohmann::json msg;
312     if (!telemetry::fillReport(msg, reportId, var))
313     {
314         BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus "
315                          "Report with id {}",
316                          reportId);
317         return;
318     }
319 
320     // Context is set by user during Event subscription and it must be
321     // set for MetricReport response.
322     if (!userSub->customText.empty())
323     {
324         msg["Context"] = userSub->customText;
325     }
326 
327     std::string strMsg =
328         msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
329     sendEventToSubscriber(std::move(strMsg));
330 }
331 
332 void Subscription::updateRetryConfig(uint32_t retryAttempts,
333                                      uint32_t retryTimeoutInterval)
334 {
335     if (policy == nullptr)
336     {
337         BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set");
338         return;
339     }
340     policy->maxRetryAttempts = retryAttempts;
341     policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval);
342 }
343 
344 uint64_t Subscription::getEventSeqNum() const
345 {
346     return eventSeqNum;
347 }
348 
349 bool Subscription::matchSseId(const crow::sse_socket::Connection& thisConn)
350 {
351     return &thisConn == sseConn;
352 }
353 
354 // Check used to indicate what response codes are valid as part of our retry
355 // policy.  2XX is considered acceptable
356 boost::system::error_code Subscription::retryRespHandler(unsigned int respCode)
357 {
358     BMCWEB_LOG_DEBUG("Checking response code validity for SubscriptionEvent");
359     if ((respCode < 200) || (respCode >= 300))
360     {
361         return boost::system::errc::make_error_code(
362             boost::system::errc::result_out_of_range);
363     }
364 
365     // Return 0 if the response code is valid
366     return boost::system::errc::make_error_code(boost::system::errc::success);
367 }
368 
369 } // namespace redfish
370