blob: 6e69f20eac2597ff2f214723e3d060a9e183cb57 [file] [log] [blame]
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001/*
2// Copyright (c) 2018 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
John Edward Broadbent7e860f12021-04-08 15:57:16 -070018#include <app.hpp>
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020019#include <boost/container/flat_map.hpp>
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +010020#include <boost/process/async_pipe.hpp>
21#include <boost/type_traits/has_dereference.hpp>
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020022#include <utils/json_utils.hpp>
23// for GetObjectType and ManagedObjectType
Ed Tanoused398212021-06-09 17:05:54 -070024
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +020025#include <account_service.hpp>
Anna Platash9e319cf2020-11-17 10:18:31 +010026#include <boost/url/url_view.hpp>
Ed Tanoused398212021-06-09 17:05:54 -070027#include <registries/privilege_registry.hpp>
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020028
29namespace redfish
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020030{
Anna Platash9e319cf2020-11-17 10:18:31 +010031/**
32 * @brief Function extracts transfer protocol name from URI.
33 */
Ed Tanous22db1722021-06-09 10:53:51 -070034inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri)
Anna Platash9e319cf2020-11-17 10:18:31 +010035{
36 try
37 {
38 std::string_view scheme = boost::urls::url_view(imageUri).scheme();
39 if (scheme == "smb")
40 {
41 return "CIFS";
42 }
Ed Tanous22db1722021-06-09 10:53:51 -070043 if (scheme == "https")
Anna Platash9e319cf2020-11-17 10:18:31 +010044 {
45 return "HTTPS";
46 }
47 }
48 catch (std::exception& p)
49 {
50 BMCWEB_LOG_ERROR << p.what();
51 }
52 return "None";
53}
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020054
55/**
56 * @brief Read all known properties from VM object interfaces
57 */
Ed Tanous22db1722021-06-09 10:53:51 -070058inline void
zhanghch058d1b46d2021-04-01 11:18:24 +080059 vmParseInterfaceObject(const DbusInterfaceType& interface,
60 const std::shared_ptr<bmcweb::AsyncResp>& aResp)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020061{
62 const auto mountPointIface =
63 interface.find("xyz.openbmc_project.VirtualMedia.MountPoint");
64 if (mountPointIface == interface.cend())
65 {
66 BMCWEB_LOG_DEBUG << "Interface MountPoint not found";
67 return;
68 }
69
70 const auto processIface =
71 interface.find("xyz.openbmc_project.VirtualMedia.Process");
72 if (processIface == interface.cend())
73 {
74 BMCWEB_LOG_DEBUG << "Interface Process not found";
75 return;
76 }
77
78 const auto endpointIdProperty = mountPointIface->second.find("EndpointId");
79 if (endpointIdProperty == mountPointIface->second.cend())
80 {
81 BMCWEB_LOG_DEBUG << "Property EndpointId not found";
82 return;
83 }
84
85 const auto activeProperty = processIface->second.find("Active");
86 if (activeProperty == processIface->second.cend())
87 {
88 BMCWEB_LOG_DEBUG << "Property Active not found";
89 return;
90 }
91
Gunnar Mills1214b7e2020-06-04 10:11:30 -050092 const bool* activeValue = std::get_if<bool>(&activeProperty->second);
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +020093 if (!activeValue)
94 {
95 BMCWEB_LOG_DEBUG << "Value Active not found";
96 return;
97 }
98
Gunnar Mills1214b7e2020-06-04 10:11:30 -050099 const std::string* endpointIdValue =
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200100 std::get_if<std::string>(&endpointIdProperty->second);
101 if (endpointIdValue)
102 {
103 if (!endpointIdValue->empty())
104 {
105 // Proxy mode
Przemyslaw Czarnowskid04ba322020-01-21 12:41:56 +0100106 aResp->res.jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] =
107 *endpointIdValue;
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200108 aResp->res.jsonValue["TransferProtocolType"] = "OEM";
109 aResp->res.jsonValue["Inserted"] = *activeValue;
110 if (*activeValue == true)
111 {
112 aResp->res.jsonValue["ConnectedVia"] = "Applet";
113 }
114 }
115 else
116 {
117 // Legacy mode
Anna Platash9e319cf2020-11-17 10:18:31 +0100118 for (const auto& property : mountPointIface->second)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200119 {
Anna Platash9e319cf2020-11-17 10:18:31 +0100120 if (property.first == "ImageURL")
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200121 {
Anna Platash9e319cf2020-11-17 10:18:31 +0100122 const std::string* imageUrlValue =
123 std::get_if<std::string>(&property.second);
124 if (imageUrlValue && !imageUrlValue->empty())
Przemyslaw Czarnowskida4784d2020-11-06 09:58:25 +0100125 {
Anna Platash9e319cf2020-11-17 10:18:31 +0100126 std::filesystem::path filePath = *imageUrlValue;
127 if (!filePath.has_filename())
128 {
129 // this will handle https share, which not
130 // necessarily has to have filename given.
131 aResp->res.jsonValue["ImageName"] = "";
132 }
133 else
134 {
135 aResp->res.jsonValue["ImageName"] =
136 filePath.filename();
137 }
Przemyslaw Czarnowskida4784d2020-11-06 09:58:25 +0100138
Anna Platash9e319cf2020-11-17 10:18:31 +0100139 aResp->res.jsonValue["Image"] = *imageUrlValue;
140 aResp->res.jsonValue["Inserted"] = *activeValue;
141 aResp->res.jsonValue["TransferProtocolType"] =
142 getTransferProtocolTypeFromUri(*imageUrlValue);
143
144 if (*activeValue == true)
145 {
146 aResp->res.jsonValue["ConnectedVia"] = "URI";
147 }
148 }
149 }
150 else if (property.first == "WriteProtected")
151 {
152 const bool* writeProtectedValue =
153 std::get_if<bool>(&property.second);
154 if (writeProtectedValue)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200155 {
Anna Platash9e319cf2020-11-17 10:18:31 +0100156 aResp->res.jsonValue["WriteProtected"] =
157 *writeProtectedValue;
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200158 }
159 }
160 }
161 }
162 }
163}
164
165/**
166 * @brief Fill template for Virtual Media Item.
167 */
Ed Tanous22db1722021-06-09 10:53:51 -0700168inline nlohmann::json vmItemTemplate(const std::string& name,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500169 const std::string& resName)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200170{
171 nlohmann::json item;
Ed Tanous22db1722021-06-09 10:53:51 -0700172
173 std::string id = "/redfish/v1/Managers/";
174 id += name;
175 id += "/VirtualMedia/";
176 id += resName;
177 item["@odata.id"] = std::move(id);
178
Przemyslaw Czarnowskid04ba322020-01-21 12:41:56 +0100179 item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia";
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200180 item["Name"] = "Virtual Removable Media";
181 item["Id"] = resName;
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200182 item["WriteProtected"] = true;
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200183 item["MediaTypes"] = {"CD", "USBStick"};
184 item["TransferMethod"] = "Stream";
Przemyslaw Czarnowskid04ba322020-01-21 12:41:56 +0100185 item["Oem"]["OpenBMC"]["@odata.type"] =
186 "#OemVirtualMedia.v1_0_0.VirtualMedia";
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200187
188 return item;
189}
190
191/**
192 * @brief Fills collection data
193 */
Ed Tanous22db1722021-06-09 10:53:51 -0700194inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500195 const std::string& service,
196 const std::string& name)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200197{
198 BMCWEB_LOG_DEBUG << "Get available Virtual Media resources.";
199 crow::connections::systemBus->async_method_call(
200 [name, aResp{std::move(aResp)}](const boost::system::error_code ec,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500201 ManagedObjectType& subtree) {
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200202 if (ec)
203 {
204 BMCWEB_LOG_DEBUG << "DBUS response error";
205 return;
206 }
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500207 nlohmann::json& members = aResp->res.jsonValue["Members"];
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200208 members = nlohmann::json::array();
209
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500210 for (const auto& object : subtree)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200211 {
212 nlohmann::json item;
Ed Tanous2dfd18e2020-12-18 00:41:31 +0000213 std::string path = object.first.filename();
214 if (path.empty())
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200215 {
216 continue;
217 }
218
Ed Tanous22db1722021-06-09 10:53:51 -0700219 std::string id = "/redfish/v1/Managers/";
220 id += name;
221 id += "/VirtualMedia/";
222 id += path;
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200223
Ed Tanous22db1722021-06-09 10:53:51 -0700224 item["@odata.id"] = std::move(id);
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200225 members.emplace_back(std::move(item));
226 }
227 aResp->res.jsonValue["Members@odata.count"] = members.size();
228 },
229 service, "/xyz/openbmc_project/VirtualMedia",
230 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
231}
232
233/**
234 * @brief Fills data for specific resource
235 */
Ed Tanous22db1722021-06-09 10:53:51 -0700236inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500237 const std::string& service, const std::string& name,
238 const std::string& resName)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200239{
240 BMCWEB_LOG_DEBUG << "Get Virtual Media resource data.";
241
242 crow::connections::systemBus->async_method_call(
243 [resName, name, aResp](const boost::system::error_code ec,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500244 ManagedObjectType& subtree) {
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200245 if (ec)
246 {
247 BMCWEB_LOG_DEBUG << "DBUS response error";
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200248
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200249 return;
250 }
251
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500252 for (auto& item : subtree)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200253 {
Ed Tanous2dfd18e2020-12-18 00:41:31 +0000254 std::string thispath = item.first.filename();
255 if (thispath.empty())
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200256 {
257 continue;
258 }
259
Ed Tanous2dfd18e2020-12-18 00:41:31 +0000260 if (thispath != resName)
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200261 {
262 continue;
263 }
264
Przemyslaw Czarnowski1a6258d2021-04-14 11:02:46 +0200265 // "Legacy"/"Proxy"
266 auto mode = item.first.parent_path();
267 // "VirtualMedia"
268 auto type = mode.parent_path();
269 if (mode.filename().empty() || type.filename().empty())
270 {
271 continue;
272 }
273
274 if (type.filename() != "VirtualMedia")
275 {
276 continue;
277 }
278
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200279 aResp->res.jsonValue = vmItemTemplate(name, resName);
Ed Tanous22db1722021-06-09 10:53:51 -0700280 std::string actionsId = "/redfish/v1/Managers/";
281 actionsId += name;
282 actionsId += "/VirtualMedia/";
283 actionsId += resName;
284 actionsId += "/Actions";
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200285
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200286 // Check if dbus path is Legacy type
Przemyslaw Czarnowski1a6258d2021-04-14 11:02:46 +0200287 if (mode.filename() == "Legacy")
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200288 {
289 aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"]
290 ["target"] =
Ed Tanous22db1722021-06-09 10:53:51 -0700291 actionsId + "/VirtualMedia.InsertMedia";
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200292 }
293
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200294 vmParseInterfaceObject(item.second, aResp);
295
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200296 aResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"]
297 ["target"] =
Ed Tanous22db1722021-06-09 10:53:51 -0700298 actionsId + "/VirtualMedia.EjectMedia";
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200299
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200300 return;
301 }
302
303 messages::resourceNotFound(
Przemyslaw Czarnowskid04ba322020-01-21 12:41:56 +0100304 aResp->res, "#VirtualMedia.v1_3_0.VirtualMedia", resName);
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +0200305 },
306 service, "/xyz/openbmc_project/VirtualMedia",
307 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
308}
309
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200310/**
Ed Tanous22db1722021-06-09 10:53:51 -0700311 * @brief Transfer protocols supported for InsertMedia action.
312 *
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200313 */
Ed Tanous22db1722021-06-09 10:53:51 -0700314enum class TransferProtocol
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200315{
Ed Tanous22db1722021-06-09 10:53:51 -0700316 https,
317 smb,
318 invalid
319};
320
321/**
322 * @brief Function extracts transfer protocol type from URI.
323 *
324 */
325inline std::optional<TransferProtocol>
326 getTransferProtocolFromUri(const std::string& imageUri)
327{
328 try
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200329 {
Ed Tanous22db1722021-06-09 10:53:51 -0700330 std::string_view scheme = boost::urls::url_view(imageUri).scheme();
331 if (scheme == "smb")
Agata Olenderc6f4e012020-03-11 15:19:07 +0100332 {
333 return TransferProtocol::smb;
334 }
Ed Tanous22db1722021-06-09 10:53:51 -0700335 if (scheme == "https")
Agata Olenderc6f4e012020-03-11 15:19:07 +0100336 {
337 return TransferProtocol::https;
338 }
Ed Tanous22db1722021-06-09 10:53:51 -0700339 if (!scheme.empty())
340 {
341 return TransferProtocol::invalid;
342 }
Agata Olenderc6f4e012020-03-11 15:19:07 +0100343 }
Ed Tanous22db1722021-06-09 10:53:51 -0700344 catch (std::exception& p)
Agata Olenderc6f4e012020-03-11 15:19:07 +0100345 {
Ed Tanous22db1722021-06-09 10:53:51 -0700346 BMCWEB_LOG_ERROR << p.what();
Agata Olenderc6f4e012020-03-11 15:19:07 +0100347 }
348
Ed Tanous22db1722021-06-09 10:53:51 -0700349 return {};
350}
351
352/**
353 * @brief Function convert transfer protocol from string param.
354 *
355 */
356inline std::optional<TransferProtocol> getTransferProtocolFromParam(
357 const std::optional<std::string>& transferProtocolType)
358{
359 if (transferProtocolType == std::nullopt)
Agata Olenderc6f4e012020-03-11 15:19:07 +0100360 {
Ed Tanous22db1722021-06-09 10:53:51 -0700361 return {};
Agata Olenderc6f4e012020-03-11 15:19:07 +0100362 }
363
Ed Tanous22db1722021-06-09 10:53:51 -0700364 if (*transferProtocolType == "CIFS")
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200365 {
Ed Tanous22db1722021-06-09 10:53:51 -0700366 return TransferProtocol::smb;
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200367 }
368
Ed Tanous22db1722021-06-09 10:53:51 -0700369 if (*transferProtocolType == "HTTPS")
370 {
371 return TransferProtocol::https;
372 }
373
374 return TransferProtocol::invalid;
375}
376
377/**
378 * @brief Function extends URI with transfer protocol type.
379 *
380 */
381inline std::string
382 getUriWithTransferProtocol(const std::string& imageUri,
383 const TransferProtocol& transferProtocol)
384{
385 if (transferProtocol == TransferProtocol::smb)
386 {
387 return "smb://" + imageUri;
388 }
389
390 if (transferProtocol == TransferProtocol::https)
391 {
392 return "https://" + imageUri;
393 }
394
395 return imageUri;
396}
397
398/**
399 * @brief Function validate parameters of insert media request.
400 *
401 */
402inline bool
403 validateParams(crow::Response& res, std::string& imageUrl,
404 const std::optional<bool>& inserted,
405 const std::optional<std::string>& transferMethod,
406 const std::optional<std::string>& transferProtocolType)
407{
408 BMCWEB_LOG_DEBUG << "Validation started";
409 // required param imageUrl must not be empty
410 if (imageUrl.empty())
411 {
412 BMCWEB_LOG_ERROR << "Request action parameter Image is empty.";
413
414 messages::propertyValueFormatError(res, "<empty>", "Image");
415
416 return false;
417 }
418
419 // optional param inserted must be true
420 if ((inserted != std::nullopt) && (*inserted != true))
421 {
422 BMCWEB_LOG_ERROR
423 << "Request action optional parameter Inserted must be true.";
424
425 messages::actionParameterNotSupported(res, "Inserted", "InsertMedia");
426
427 return false;
428 }
429
430 // optional param transferMethod must be stream
431 if ((transferMethod != std::nullopt) && (*transferMethod != "Stream"))
432 {
433 BMCWEB_LOG_ERROR << "Request action optional parameter "
434 "TransferMethod must be Stream.";
435
436 messages::actionParameterNotSupported(res, "TransferMethod",
437 "InsertMedia");
438
439 return false;
440 }
441
442 std::optional<TransferProtocol> uriTransferProtocolType =
443 getTransferProtocolFromUri(imageUrl);
444
445 std::optional<TransferProtocol> paramTransferProtocolType =
446 getTransferProtocolFromParam(transferProtocolType);
447
448 // ImageUrl does not contain valid protocol type
449 if (*uriTransferProtocolType == TransferProtocol::invalid)
450 {
451 BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must "
452 "contain specified protocol type from list: "
453 "(smb, https).";
454
455 messages::resourceAtUriInUnknownFormat(res, imageUrl);
456
457 return false;
458 }
459
460 // transferProtocolType should contain value from list
461 if (*paramTransferProtocolType == TransferProtocol::invalid)
462 {
463 BMCWEB_LOG_ERROR << "Request action parameter TransferProtocolType "
464 "must be provided with value from list: "
465 "(CIFS, HTTPS).";
466
467 messages::propertyValueNotInList(res, *transferProtocolType,
468 "TransferProtocolType");
469 return false;
470 }
471
472 // valid transfer protocol not provided either with URI nor param
473 if ((uriTransferProtocolType == std::nullopt) &&
474 (paramTransferProtocolType == std::nullopt))
475 {
476 BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must "
477 "contain specified protocol type or param "
478 "TransferProtocolType must be provided.";
479
480 messages::resourceAtUriInUnknownFormat(res, imageUrl);
481
482 return false;
483 }
484
485 // valid transfer protocol provided both with URI and param
486 if ((paramTransferProtocolType != std::nullopt) &&
487 (uriTransferProtocolType != std::nullopt))
488 {
489 // check if protocol is the same for URI and param
490 if (*paramTransferProtocolType != *uriTransferProtocolType)
491 {
492 BMCWEB_LOG_ERROR << "Request action parameter "
493 "TransferProtocolType must contain the "
494 "same protocol type as protocol type "
495 "provided with param imageUrl.";
496
497 messages::actionParameterValueTypeError(res, *transferProtocolType,
498 "TransferProtocolType",
499 "InsertMedia");
500
501 return false;
502 }
503 }
504
505 // validation passed
506 // add protocol to URI if needed
507 if (uriTransferProtocolType == std::nullopt)
508 {
509 imageUrl =
510 getUriWithTransferProtocol(imageUrl, *paramTransferProtocolType);
511 }
512
513 return true;
514}
515
516template <typename T>
517static void secureCleanup(T& value)
518{
519 auto raw = const_cast<typename T::value_type*>(value.data());
520 explicit_bzero(raw, value.size() * sizeof(*raw));
521}
522
523class Credentials
524{
525 public:
526 Credentials(std::string&& user, std::string&& password) :
527 userBuf(std::move(user)), passBuf(std::move(password))
528 {}
529
530 ~Credentials()
531 {
532 secureCleanup(userBuf);
533 secureCleanup(passBuf);
534 }
535
536 const std::string& user()
537 {
538 return userBuf;
539 }
540
541 const std::string& password()
542 {
543 return passBuf;
544 }
545
546 Credentials() = delete;
547 Credentials(const Credentials&) = delete;
548 Credentials& operator=(const Credentials&) = delete;
549
550 private:
551 std::string userBuf;
552 std::string passBuf;
553};
554
555class CredentialsProvider
556{
557 public:
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500558 template <typename T>
Ed Tanous22db1722021-06-09 10:53:51 -0700559 struct Deleter
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100560 {
Ed Tanous22db1722021-06-09 10:53:51 -0700561 void operator()(T* buff) const
562 {
563 if (buff)
564 {
565 secureCleanup(*buff);
566 delete buff;
567 }
568 }
569 };
570
571 using Buffer = std::vector<char>;
572 using SecureBuffer = std::unique_ptr<Buffer, Deleter<Buffer>>;
573 // Using explicit definition instead of std::function to avoid implicit
574 // conversions eg. stack copy instead of reference
575 using FormatterFunc = void(const std::string& username,
576 const std::string& password, Buffer& dest);
577
578 CredentialsProvider(std::string&& user, std::string&& password) :
579 credentials(std::move(user), std::move(password))
580 {}
581
582 const std::string& user()
583 {
584 return credentials.user();
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100585 }
586
Ed Tanous22db1722021-06-09 10:53:51 -0700587 const std::string& password()
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100588 {
Ed Tanous22db1722021-06-09 10:53:51 -0700589 return credentials.password();
590 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100591
Ed Tanous22db1722021-06-09 10:53:51 -0700592 SecureBuffer pack(FormatterFunc formatter)
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100593 {
Ed Tanous22db1722021-06-09 10:53:51 -0700594 SecureBuffer packed{new Buffer{}};
595 if (formatter)
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100596 {
Ed Tanous22db1722021-06-09 10:53:51 -0700597 formatter(credentials.user(), credentials.password(), *packed);
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100598 }
599
Ed Tanous22db1722021-06-09 10:53:51 -0700600 return packed;
601 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100602
Ed Tanous22db1722021-06-09 10:53:51 -0700603 private:
604 Credentials credentials;
605};
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100606
Ed Tanous22db1722021-06-09 10:53:51 -0700607// Wrapper for boost::async_pipe ensuring proper pipe cleanup
608template <typename Buffer>
609class Pipe
610{
611 public:
612 using unix_fd = sdbusplus::message::unix_fd;
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100613
Ed Tanous22db1722021-06-09 10:53:51 -0700614 Pipe(boost::asio::io_context& io, Buffer&& buffer) :
615 impl(io), buffer{std::move(buffer)}
616 {}
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100617
Ed Tanous22db1722021-06-09 10:53:51 -0700618 ~Pipe()
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100619 {
Ed Tanous22db1722021-06-09 10:53:51 -0700620 // Named pipe needs to be explicitly removed
621 impl.close();
622 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100623
Ed Tanous22db1722021-06-09 10:53:51 -0700624 unix_fd fd()
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200625 {
Ed Tanous22db1722021-06-09 10:53:51 -0700626 return unix_fd{impl.native_source()};
627 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100628
Ed Tanous22db1722021-06-09 10:53:51 -0700629 template <typename WriteHandler>
630 void asyncWrite(WriteHandler&& handler)
631 {
632 impl.async_write_some(data(), std::forward<WriteHandler>(handler));
633 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100634
Ed Tanous22db1722021-06-09 10:53:51 -0700635 private:
636 // Specialization for pointer types
637 template <typename B = Buffer>
638 typename std::enable_if<boost::has_dereference<B>::value,
639 boost::asio::const_buffer>::type
640 data()
641 {
642 return boost::asio::buffer(*buffer);
643 }
644
645 template <typename B = Buffer>
646 typename std::enable_if<!boost::has_dereference<B>::value,
647 boost::asio::const_buffer>::type
648 data()
649 {
650 return boost::asio::buffer(buffer);
651 }
652
653 const std::string name;
654 boost::process::async_pipe impl;
655 Buffer buffer;
656};
657
658/**
659 * @brief Function transceives data with dbus directly.
660 *
661 * All BMC state properties will be retrieved before sending reset request.
662 */
663inline void doMountVmLegacy(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
664 const std::string& service, const std::string& name,
665 const std::string& imageUrl, const bool rw,
666 std::string&& userName, std::string&& password)
667{
668 using SecurePipe = Pipe<CredentialsProvider::SecureBuffer>;
669 constexpr const size_t secretLimit = 1024;
670
671 std::shared_ptr<SecurePipe> secretPipe;
672 std::variant<int, SecurePipe::unix_fd> unixFd = -1;
673
674 if (!userName.empty() || !password.empty())
675 {
676 // Encapsulate in safe buffer
677 CredentialsProvider credentials(std::move(userName),
678 std::move(password));
679
680 // Payload must contain data + NULL delimiters
681 if (credentials.user().size() + credentials.password().size() + 2 >
682 secretLimit)
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100683 {
Ed Tanous22db1722021-06-09 10:53:51 -0700684 BMCWEB_LOG_ERROR << "Credentials too long to handle";
685 messages::unrecognizedRequestBody(asyncResp->res);
686 return;
687 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100688
Ed Tanous22db1722021-06-09 10:53:51 -0700689 // Pack secret
690 auto secret = credentials.pack(
691 [](const auto& user, const auto& pass, auto& buff) {
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100692 std::copy(user.begin(), user.end(), std::back_inserter(buff));
693 buff.push_back('\0');
694 std::copy(pass.begin(), pass.end(), std::back_inserter(buff));
695 buff.push_back('\0');
696 });
697
Ed Tanous22db1722021-06-09 10:53:51 -0700698 // Open pipe
699 secretPipe = std::make_shared<SecurePipe>(
700 crow::connections::systemBus->get_io_context(), std::move(secret));
701 unixFd = secretPipe->fd();
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100702
Ed Tanous22db1722021-06-09 10:53:51 -0700703 // Pass secret over pipe
704 secretPipe->asyncWrite(
705 [asyncResp](const boost::system::error_code& ec, std::size_t) {
706 if (ec)
707 {
708 BMCWEB_LOG_ERROR << "Failed to pass secret: " << ec;
709 messages::internalError(asyncResp->res);
710 }
711 });
712 }
Adrian Ambrożewicz988fb7b2020-01-13 18:52:46 +0100713
Ed Tanous22db1722021-06-09 10:53:51 -0700714 crow::connections::systemBus->async_method_call(
715 [asyncResp, secretPipe](const boost::system::error_code ec,
716 bool success) {
717 if (ec)
718 {
719 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
720 messages::internalError(asyncResp->res);
721 }
722 else if (!success)
723 {
724 BMCWEB_LOG_ERROR << "Service responded with error";
725 messages::generalError(asyncResp->res);
726 }
727 },
728 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
729 "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw,
730 unixFd);
731}
732
733/**
734 * @brief Function transceives data with dbus directly.
735 *
736 * All BMC state properties will be retrieved before sending reset request.
737 */
738inline void doVmAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
739 const std::string& service, const std::string& name,
740 bool legacy)
741{
742
743 // Legacy mount requires parameter with image
744 if (legacy)
745 {
Adrian Ambrożewiczd6da5be2020-01-13 18:31:01 +0100746 crow::connections::systemBus->async_method_call(
Ed Tanous22db1722021-06-09 10:53:51 -0700747 [asyncResp](const boost::system::error_code ec) {
Adrian Ambrożewiczd6da5be2020-01-13 18:31:01 +0100748 if (ec)
749 {
750 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
Ed Tanous22db1722021-06-09 10:53:51 -0700751
Adrian Ambrożewiczd6da5be2020-01-13 18:31:01 +0100752 messages::internalError(asyncResp->res);
Ed Tanous22db1722021-06-09 10:53:51 -0700753 return;
Adrian Ambrożewiczd6da5be2020-01-13 18:31:01 +0100754 }
755 },
756 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
Ed Tanous22db1722021-06-09 10:53:51 -0700757 "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount");
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200758 }
Ed Tanous22db1722021-06-09 10:53:51 -0700759 else // proxy
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200760 {
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200761 crow::connections::systemBus->async_method_call(
Ed Tanous22db1722021-06-09 10:53:51 -0700762 [asyncResp](const boost::system::error_code ec) {
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200763 if (ec)
764 {
Ed Tanous22db1722021-06-09 10:53:51 -0700765 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
766
zhanghch058d1b46d2021-04-01 11:18:24 +0800767 messages::internalError(asyncResp->res);
Ed Tanous22db1722021-06-09 10:53:51 -0700768 return;
769 }
770 },
771 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
772 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
773 }
774}
775
776inline void requestNBDVirtualMediaRoutes(App& app)
777{
778 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/"
779 "VirtualMedia.InsertMedia")
Ed Tanoused398212021-06-09 17:05:54 -0700780 .privileges(redfish::privileges::postVirtualMedia)
Ed Tanous22db1722021-06-09 10:53:51 -0700781 .methods(boost::beast::http::verb::post)(
782 [](const crow::Request& req,
783 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
784 const std::string& name, const std::string& resName) {
785 if (name != "bmc")
786 {
787 messages::resourceNotFound(asyncResp->res,
788 "VirtualMedia.Insert", resName);
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200789
790 return;
791 }
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200792
793 crow::connections::systemBus->async_method_call(
Ed Tanous22db1722021-06-09 10:53:51 -0700794 [asyncResp, req,
795 resName](const boost::system::error_code ec,
796 const GetObjectType& getObjectType) {
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200797 if (ec)
798 {
Ed Tanous22db1722021-06-09 10:53:51 -0700799 BMCWEB_LOG_ERROR
800 << "ObjectMapper::GetObject call failed: "
801 << ec;
802 messages::internalError(asyncResp->res);
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200803
804 return;
805 }
Ed Tanous22db1722021-06-09 10:53:51 -0700806 std::string service = getObjectType.begin()->first;
807 BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200808
Ed Tanous22db1722021-06-09 10:53:51 -0700809 crow::connections::systemBus->async_method_call(
810 [service, resName, req,
811 asyncResp](const boost::system::error_code ec,
812 ManagedObjectType& subtree) {
813 if (ec)
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200814 {
Ed Tanous22db1722021-06-09 10:53:51 -0700815 BMCWEB_LOG_DEBUG << "DBUS response error";
816
817 return;
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200818 }
819
Ed Tanous22db1722021-06-09 10:53:51 -0700820 for (const auto& object : subtree)
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200821 {
Ed Tanous22db1722021-06-09 10:53:51 -0700822 const std::string& path =
823 static_cast<const std::string&>(
824 object.first);
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200825
Ed Tanous22db1722021-06-09 10:53:51 -0700826 std::size_t lastIndex = path.rfind('/');
827 if (lastIndex == std::string::npos)
828 {
829 continue;
830 }
831
832 lastIndex += 1;
833
834 if (path.substr(lastIndex) == resName)
835 {
836 lastIndex = path.rfind("Proxy");
837 if (lastIndex != std::string::npos)
838 {
839 // Not possible in proxy mode
840 BMCWEB_LOG_DEBUG
841 << "InsertMedia not "
842 "allowed in proxy mode";
843 messages::resourceNotFound(
844 asyncResp->res,
845 "VirtualMedia.InsertMedia",
846 resName);
847
848 return;
849 }
850
851 lastIndex = path.rfind("Legacy");
852 if (lastIndex == std::string::npos)
853 {
854 continue;
855 }
856
857 // Legacy mode
858 std::string imageUrl;
859 std::optional<std::string> userName;
860 std::optional<std::string> password;
861 std::optional<std::string>
862 transferMethod;
863 std::optional<std::string>
864 transferProtocolType;
865 std::optional<bool> writeProtected =
866 true;
867 std::optional<bool> inserted;
868
869 // Read obligatory parameters (url of
870 // image)
871 if (!json_util::readJson(
872 req, asyncResp->res, "Image",
873 imageUrl, "WriteProtected",
874 writeProtected, "UserName",
875 userName, "Password", password,
876 "Inserted", inserted,
877 "TransferMethod",
878 transferMethod,
879 "TransferProtocolType",
880 transferProtocolType))
881 {
882 BMCWEB_LOG_DEBUG
883 << "Image is not provided";
884 return;
885 }
886
887 bool paramsValid = validateParams(
888 asyncResp->res, imageUrl, inserted,
889 transferMethod,
890 transferProtocolType);
891
892 if (paramsValid == false)
893 {
894 return;
895 }
896
897 // manager is irrelevant for
898 // VirtualMedia dbus calls
899 doMountVmLegacy(asyncResp, service,
900 resName, imageUrl,
901 !(*writeProtected),
902 std::move(*userName),
903 std::move(*password));
904
905 return;
906 }
907 }
908 BMCWEB_LOG_DEBUG << "Parent item not found";
909 messages::resourceNotFound(
910 asyncResp->res, "VirtualMedia", resName);
911 },
912 service, "/xyz/openbmc_project/VirtualMedia",
913 "org.freedesktop.DBus.ObjectManager",
914 "GetManagedObjects");
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200915 },
Ed Tanous22db1722021-06-09 10:53:51 -0700916 "xyz.openbmc_project.ObjectMapper",
917 "/xyz/openbmc_project/object_mapper",
918 "xyz.openbmc_project.ObjectMapper", "GetObject",
919 "/xyz/openbmc_project/VirtualMedia",
920 std::array<const char*, 0>());
921 });
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200922
Ed Tanous22db1722021-06-09 10:53:51 -0700923 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/"
924 "VirtualMedia.EjectMedia")
Ed Tanoused398212021-06-09 17:05:54 -0700925 .privileges(redfish::privileges::postVirtualMedia)
Ed Tanous22db1722021-06-09 10:53:51 -0700926 .methods(boost::beast::http::verb::post)(
927 [](const crow::Request& req,
928 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
929 const std::string& name, const std::string& resName) {
930 if (name != "bmc")
931 {
932 messages::resourceNotFound(asyncResp->res,
933 "VirtualMedia.Eject", resName);
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200934
Ed Tanous22db1722021-06-09 10:53:51 -0700935 return;
936 }
Przemyslaw Czarnowskie13c2762019-09-02 17:32:43 +0200937
Ed Tanous22db1722021-06-09 10:53:51 -0700938 crow::connections::systemBus->async_method_call(
939 [asyncResp, req,
940 resName](const boost::system::error_code ec,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500941 const GetObjectType& getObjectType) {
Ed Tanous22db1722021-06-09 10:53:51 -0700942 if (ec)
943 {
944 BMCWEB_LOG_ERROR
945 << "ObjectMapper::GetObject call failed: "
946 << ec;
947 messages::internalError(asyncResp->res);
948
949 return;
950 }
951 std::string service = getObjectType.begin()->first;
952 BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
953
954 crow::connections::systemBus->async_method_call(
955 [resName, service, req, asyncResp{asyncResp}](
956 const boost::system::error_code ec,
957 ManagedObjectType& subtree) {
958 if (ec)
959 {
960 BMCWEB_LOG_DEBUG << "DBUS response error";
961
962 return;
963 }
964
965 for (const auto& object : subtree)
966 {
967 const std::string& path =
968 static_cast<const std::string&>(
969 object.first);
970
971 std::size_t lastIndex = path.rfind('/');
972 if (lastIndex == std::string::npos)
973 {
974 continue;
975 }
976
977 lastIndex += 1;
978
979 if (path.substr(lastIndex) == resName)
980 {
981 lastIndex = path.rfind("Proxy");
982 if (lastIndex != std::string::npos)
983 {
984 // Proxy mode
985 doVmAction(asyncResp, service,
986 resName, false);
987 }
988
989 lastIndex = path.rfind("Legacy");
990 if (lastIndex != std::string::npos)
991 {
992 // Legacy mode
993 doVmAction(asyncResp, service,
994 resName, true);
995 }
996
997 return;
998 }
999 }
1000 BMCWEB_LOG_DEBUG << "Parent item not found";
1001 messages::resourceNotFound(
1002 asyncResp->res, "VirtualMedia", resName);
1003 },
1004 service, "/xyz/openbmc_project/VirtualMedia",
1005 "org.freedesktop.DBus.ObjectManager",
1006 "GetManagedObjects");
1007 },
1008 "xyz.openbmc_project.ObjectMapper",
1009 "/xyz/openbmc_project/object_mapper",
1010 "xyz.openbmc_project.ObjectMapper", "GetObject",
1011 "/xyz/openbmc_project/VirtualMedia",
1012 std::array<const char*, 0>());
1013 });
1014 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/")
Ed Tanoused398212021-06-09 17:05:54 -07001015 .privileges(redfish::privileges::getVirtualMediaCollection)
Ed Tanous22db1722021-06-09 10:53:51 -07001016 .methods(boost::beast::http::verb::get)(
1017 [](const crow::Request& /* req */,
1018 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1019 const std::string& name) {
1020 if (name != "bmc")
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001021 {
Ed Tanous22db1722021-06-09 10:53:51 -07001022 messages::resourceNotFound(asyncResp->res, "VirtualMedia",
1023 name);
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001024
1025 return;
1026 }
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001027
Ed Tanous22db1722021-06-09 10:53:51 -07001028 asyncResp->res.jsonValue["@odata.type"] =
1029 "#VirtualMediaCollection.VirtualMediaCollection";
1030 asyncResp->res.jsonValue["Name"] = "Virtual Media Services";
1031 asyncResp->res.jsonValue["@odata.id"] =
1032 "/redfish/v1/Managers/" + name + "/VirtualMedia";
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001033
Ed Tanous22db1722021-06-09 10:53:51 -07001034 crow::connections::systemBus->async_method_call(
1035 [asyncResp, name](const boost::system::error_code ec,
1036 const GetObjectType& getObjectType) {
1037 if (ec)
1038 {
1039 BMCWEB_LOG_ERROR
1040 << "ObjectMapper::GetObject call failed: "
1041 << ec;
1042 messages::internalError(asyncResp->res);
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001043
Ed Tanous22db1722021-06-09 10:53:51 -07001044 return;
1045 }
1046 std::string service = getObjectType.begin()->first;
1047 BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001048
Ed Tanous22db1722021-06-09 10:53:51 -07001049 getVmResourceList(asyncResp, service, name);
1050 },
1051 "xyz.openbmc_project.ObjectMapper",
1052 "/xyz/openbmc_project/object_mapper",
1053 "xyz.openbmc_project.ObjectMapper", "GetObject",
1054 "/xyz/openbmc_project/VirtualMedia",
1055 std::array<const char*, 0>());
1056 });
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001057
Ed Tanous22db1722021-06-09 10:53:51 -07001058 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -07001059 .privileges(redfish::privileges::getVirtualMedia)
Ed Tanous22db1722021-06-09 10:53:51 -07001060 .methods(boost::beast::http::verb::get)(
1061 [](const crow::Request& /* req */,
1062 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1063 const std::string& name, const std::string& resName) {
1064 if (name != "bmc")
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001065 {
Ed Tanous22db1722021-06-09 10:53:51 -07001066 messages::resourceNotFound(asyncResp->res, "VirtualMedia",
1067 resName);
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001068
1069 return;
1070 }
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001071
Ed Tanous22db1722021-06-09 10:53:51 -07001072 crow::connections::systemBus->async_method_call(
1073 [asyncResp, name,
1074 resName](const boost::system::error_code ec,
1075 const GetObjectType& getObjectType) {
1076 if (ec)
1077 {
1078 BMCWEB_LOG_ERROR
1079 << "ObjectMapper::GetObject call failed: "
1080 << ec;
1081 messages::internalError(asyncResp->res);
1082
1083 return;
1084 }
1085 std::string service = getObjectType.begin()->first;
1086 BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
1087
1088 getVmData(asyncResp, service, name, resName);
1089 },
1090 "xyz.openbmc_project.ObjectMapper",
1091 "/xyz/openbmc_project/object_mapper",
1092 "xyz.openbmc_project.ObjectMapper", "GetObject",
1093 "/xyz/openbmc_project/VirtualMedia",
1094 std::array<const char*, 0>());
1095 });
1096}
Przemyslaw Czarnowski107077d2019-07-11 10:16:43 +02001097
1098} // namespace redfish