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