blob: bf1a25014340ccc03c888950e4ff2d4f706aad56 [file] [log] [blame]
Jennifer Lee729dae72018-04-24 15:59:34 -07001/*
Ed Tanous6be832e2024-09-10 11:44:48 -07002Copyright (c) 2018 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
Jennifer Lee729dae72018-04-24 15:59:34 -070015*/
16#pragma once
17
Tejas Patild61e5192021-06-04 15:49:35 +053018#include "bmcweb_config.h"
19
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080020#include "app.hpp"
21#include "dbus_utility.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -070022#include "error_messages.hpp"
Ed Tanous757178a2024-04-03 14:32:38 -070023#include "generated/enums/update_service.hpp"
George Liu0ed80c82020-05-12 16:06:27 +080024#include "multipart_parser.hpp"
Ed Tanous2c6ffdb2023-06-28 11:28:38 -070025#include "ossl_random.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080026#include "query.hpp"
27#include "registries/privilege_registry.hpp"
Ed Tanousa8e884f2023-01-13 17:40:03 -080028#include "task.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -070029#include "task_messages.hpp"
John Edward Broadbent08d81ad2022-05-17 20:00:23 -070030#include "utils/collection.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080031#include "utils/dbus_utils.hpp"
Ed Tanous5b904292024-04-16 11:10:17 -070032#include "utils/json_utils.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080033#include "utils/sw_utils.hpp"
34
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070035#include <sys/mman.h>
36
George Liue99073f2022-12-09 11:06:16 +080037#include <boost/system/error_code.hpp>
Ed Tanousef4c65b2023-04-24 15:28:50 -070038#include <boost/url/format.hpp>
Jonathan Doman1e1e5982021-06-11 09:36:17 -070039#include <sdbusplus/asio/property.hpp>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080040#include <sdbusplus/bus/match.hpp>
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +020041#include <sdbusplus/unpack_properties.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050042
George Liu2b731192023-01-11 16:27:13 +080043#include <array>
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070044#include <cstddef>
George Liu0ed80c82020-05-12 16:06:27 +080045#include <filesystem>
Jagpal Singh Gillc71b6c92024-04-29 16:50:53 -070046#include <functional>
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070047#include <iterator>
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -070048#include <memory>
Ed Tanous7cb59f62022-05-05 11:48:31 -070049#include <optional>
50#include <string>
George Liu2b731192023-01-11 16:27:13 +080051#include <string_view>
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070052#include <unordered_map>
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -070053#include <vector>
George Liu2b731192023-01-11 16:27:13 +080054
Ed Tanous1abe55e2018-09-05 08:30:59 -070055namespace redfish
56{
Ed Tanous27826b52018-10-29 11:40:58 -070057
Andrew Geissler0e7de462019-03-04 19:11:54 -060058// Match signals added on software path
Ed Tanouscf9e4172022-12-21 09:30:16 -080059// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Patrick Williams59d494e2022-07-22 19:26:55 -050060static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
Ed Tanouscf9e4172022-12-21 09:30:16 -080061// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Patrick Williams59d494e2022-07-22 19:26:55 -050062static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
Andrew Geissler0e7de462019-03-04 19:11:54 -060063// Only allow one update at a time
Ed Tanouscf9e4172022-12-21 09:30:16 -080064// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Andrew Geissler0e7de462019-03-04 19:11:54 -060065static bool fwUpdateInProgress = false;
Andrew Geissler86adcd62019-04-18 10:58:05 -050066// Timer for software available
Ed Tanouscf9e4172022-12-21 09:30:16 -080067// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Ed Tanous271584a2019-07-09 16:24:22 -070068static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
Andrew Geissler86adcd62019-04-18 10:58:05 -050069
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -070070struct MemoryFileDescriptor
71{
72 int fd = -1;
73
74 explicit MemoryFileDescriptor(const std::string& filename) :
75 fd(memfd_create(filename.c_str(), 0))
76 {}
77
78 MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
79 MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
80 {
81 other.fd = -1;
82 }
83 MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
84 MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
85
86 ~MemoryFileDescriptor()
87 {
88 if (fd != -1)
89 {
90 close(fd);
91 }
92 }
93
94 bool rewind() const
95 {
96 if (lseek(fd, 0, SEEK_SET) == -1)
97 {
98 BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
99 return false;
100 }
101 return true;
102 }
103};
104
Ed Tanousdf254f22024-04-01 13:25:46 -0700105inline void cleanUp()
Andrew Geissler86adcd62019-04-18 10:58:05 -0500106{
107 fwUpdateInProgress = false;
108 fwUpdateMatcher = nullptr;
James Feist4cde5d92020-06-11 10:39:55 -0700109 fwUpdateErrorMatcher = nullptr;
Andrew Geissler86adcd62019-04-18 10:58:05 -0500110}
Ed Tanousdf254f22024-04-01 13:25:46 -0700111
112inline void activateImage(const std::string& objPath,
113 const std::string& service)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500114{
Ed Tanous62598e32023-07-17 17:06:25 -0700115 BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
George Liu9ae226f2023-06-21 17:56:46 +0800116 sdbusplus::asio::setProperty(
117 *crow::connections::systemBus, service, objPath,
118 "xyz.openbmc_project.Software.Activation", "RequestedActivation",
119 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
Ed Tanous8b242752023-06-27 17:17:13 -0700120 [](const boost::system::error_code& ec) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400121 if (ec)
122 {
123 BMCWEB_LOG_DEBUG("error_code = {}", ec);
124 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
125 }
126 });
Andrew Geissler86adcd62019-04-18 10:58:05 -0500127}
Andrew Geissler0554c982019-04-23 14:40:12 -0500128
Jagpal Singh Gillc71b6c92024-04-29 16:50:53 -0700129inline bool handleCreateTask(const boost::system::error_code& ec2,
130 sdbusplus::message_t& msg,
131 const std::shared_ptr<task::TaskData>& taskData)
132{
133 if (ec2)
134 {
135 return task::completed;
136 }
137
138 std::string iface;
139 dbus::utility::DBusPropertiesMap values;
140
141 std::string index = std::to_string(taskData->index);
142 msg.read(iface, values);
143
144 if (iface == "xyz.openbmc_project.Software.Activation")
145 {
146 const std::string* state = nullptr;
147 for (const auto& property : values)
148 {
149 if (property.first == "Activation")
150 {
151 state = std::get_if<std::string>(&property.second);
152 if (state == nullptr)
153 {
154 taskData->messages.emplace_back(messages::internalError());
155 return task::completed;
156 }
157 }
158 }
159
160 if (state == nullptr)
161 {
162 return !task::completed;
163 }
164
165 if (state->ends_with("Invalid") || state->ends_with("Failed"))
166 {
167 taskData->state = "Exception";
168 taskData->status = "Warning";
169 taskData->messages.emplace_back(messages::taskAborted(index));
170 return task::completed;
171 }
172
173 if (state->ends_with("Staged"))
174 {
175 taskData->state = "Stopping";
176 taskData->messages.emplace_back(messages::taskPaused(index));
177
178 // its staged, set a long timer to
179 // allow them time to complete the
180 // update (probably cycle the
181 // system) if this expires then
182 // task will be canceled
183 taskData->extendTimer(std::chrono::hours(5));
184 return !task::completed;
185 }
186
187 if (state->ends_with("Active"))
188 {
189 taskData->messages.emplace_back(messages::taskCompletedOK(index));
190 taskData->state = "Completed";
191 return task::completed;
192 }
193 }
194 else if (iface == "xyz.openbmc_project.Software.ActivationProgress")
195 {
196 const uint8_t* progress = nullptr;
197 for (const auto& property : values)
198 {
199 if (property.first == "Progress")
200 {
201 progress = std::get_if<uint8_t>(&property.second);
202 if (progress == nullptr)
203 {
204 taskData->messages.emplace_back(messages::internalError());
205 return task::completed;
206 }
207 }
208 }
209
210 if (progress == nullptr)
211 {
212 return !task::completed;
213 }
214 taskData->percentComplete = *progress;
215 taskData->messages.emplace_back(
216 messages::taskProgressChanged(index, *progress));
217
218 // if we're getting status updates it's
219 // still alive, update timer
220 taskData->extendTimer(std::chrono::minutes(5));
221 }
222
223 // as firmware update often results in a
224 // reboot, the task may never "complete"
225 // unless it is an error
226
227 return !task::completed;
228}
229
230inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
231 task::Payload&& payload,
232 const sdbusplus::message::object_path& objPath)
233{
234 std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
235 std::bind_front(handleCreateTask),
236 "type='signal',interface='org.freedesktop.DBus.Properties',"
237 "member='PropertiesChanged',path='" +
238 objPath.str + "'");
239 task->startTimer(std::chrono::minutes(5));
240 task->populateResp(asyncResp->res);
241 task->payload.emplace(std::move(payload));
242}
243
Andrew Geissler0554c982019-04-23 14:40:12 -0500244// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
245// then no asyncResp updates will occur
Ed Tanous4ff0f1f2024-09-04 17:27:37 -0700246inline void
zhanghch058d1b46d2021-04-01 11:18:24 +0800247 softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
Patrick Williams59d494e2022-07-22 19:26:55 -0500248 sdbusplus::message_t& m, task::Payload&& payload)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500249{
Michael Shen80f79a42023-08-24 13:41:53 +0000250 dbus::utility::DBusInterfacesMap interfacesProperties;
Andrew Geissler86adcd62019-04-18 10:58:05 -0500251
252 sdbusplus::message::object_path objPath;
253
254 m.read(objPath, interfacesProperties);
255
Ed Tanous62598e32023-07-17 17:06:25 -0700256 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
Ed Tanouse3eb3d62022-12-21 11:56:07 -0800257 for (const auto& interface : interfacesProperties)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500258 {
Ed Tanous62598e32023-07-17 17:06:25 -0700259 BMCWEB_LOG_DEBUG("interface = {}", interface.first);
Andrew Geissler86adcd62019-04-18 10:58:05 -0500260
261 if (interface.first == "xyz.openbmc_project.Software.Activation")
262 {
Andrew Geissler86adcd62019-04-18 10:58:05 -0500263 // Retrieve service and activate
George Liu2b731192023-01-11 16:27:13 +0800264 constexpr std::array<std::string_view, 1> interfaces = {
265 "xyz.openbmc_project.Software.Activation"};
266 dbus::utility::getDbusObject(
267 objPath.str, interfaces,
Ed Tanousa3e65892021-09-16 14:13:20 -0700268 [objPath, asyncResp, payload(std::move(payload))](
Ed Tanous8b242752023-06-27 17:17:13 -0700269 const boost::system::error_code& ec,
Ed Tanousa3e65892021-09-16 14:13:20 -0700270 const std::vector<
271 std::pair<std::string, std::vector<std::string>>>&
272 objInfo) mutable {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400273 if (ec)
Andrew Geissler0554c982019-04-23 14:40:12 -0500274 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400275 BMCWEB_LOG_DEBUG("error_code = {}", ec);
276 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
277 if (asyncResp)
278 {
279 messages::internalError(asyncResp->res);
280 }
281 cleanUp();
282 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700283 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400284 // Ensure we only got one service back
285 if (objInfo.size() != 1)
Ed Tanous002d39b2022-05-31 08:59:27 -0700286 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400287 BMCWEB_LOG_ERROR("Invalid Object Size {}",
288 objInfo.size());
289 if (asyncResp)
290 {
291 messages::internalError(asyncResp->res);
292 }
293 cleanUp();
294 return;
Ed Tanous002d39b2022-05-31 08:59:27 -0700295 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400296 // cancel timer only when
297 // xyz.openbmc_project.Software.Activation interface
298 // is added
299 fwAvailableTimer = nullptr;
Ed Tanous002d39b2022-05-31 08:59:27 -0700300
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400301 activateImage(objPath.str, objInfo[0].first);
302 if (asyncResp)
303 {
304 createTask(asyncResp, std::move(payload), objPath);
305 }
306 fwUpdateInProgress = false;
307 });
Patrick Williams62bafc02022-09-08 17:35:35 -0500308
309 break;
Andrew Geissler86adcd62019-04-18 10:58:05 -0500310 }
311 }
312}
313
Myung Bae8549b952023-08-16 15:18:19 -0400314inline void afterAvailbleTimerAsyncWait(
315 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
316 const boost::system::error_code& ec)
317{
318 cleanUp();
319 if (ec == boost::asio::error::operation_aborted)
320 {
321 // expected, we were canceled before the timer completed.
322 return;
323 }
324 BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
325 BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
326 if (ec)
327 {
328 BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
329 return;
330 }
331 if (asyncResp)
332 {
333 redfish::messages::internalError(asyncResp->res);
334 }
335}
336
337inline void
338 handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339 const std::string& url, const std::string& type)
340{
341 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
342 {
343 redfish::messages::invalidUpload(asyncResp->res, url,
344 "Invalid archive");
345 }
346 else if (type ==
347 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
348 {
349 redfish::messages::invalidUpload(asyncResp->res, url,
350 "Invalid manifest");
351 }
352 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
353 {
354 redfish::messages::invalidUpload(asyncResp->res, url,
355 "Invalid image format");
356 }
357 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
358 {
359 redfish::messages::invalidUpload(asyncResp->res, url,
360 "Image version already exists");
361
362 redfish::messages::resourceAlreadyExists(
363 asyncResp->res, "UpdateService", "Version", "uploaded version");
364 }
365 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
366 {
367 redfish::messages::resourceExhaustion(asyncResp->res, url);
368 }
Myung Bae4034a652023-08-17 08:47:35 -0400369 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
Myung Bae8549b952023-08-16 15:18:19 -0400370 {
Myung Bae4034a652023-08-17 08:47:35 -0400371 redfish::messages::invalidUpload(asyncResp->res, url,
372 "Incompatible image version");
373 }
374 else if (type ==
375 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
376 {
377 redfish::messages::invalidUpload(asyncResp->res, url,
378 "Update Access Key Expired");
379 }
380 else if (type ==
381 "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
382 {
383 redfish::messages::invalidUpload(asyncResp->res, url,
384 "Invalid image signature");
385 }
386 else if (type ==
387 "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
388 type == "xyz.openbmc_project.Software.Version.Error.HostFile")
389 {
390 BMCWEB_LOG_ERROR("Software Image Error type={}", type);
Myung Bae8549b952023-08-16 15:18:19 -0400391 redfish::messages::internalError(asyncResp->res);
392 }
Myung Bae4034a652023-08-17 08:47:35 -0400393 else
394 {
395 // Unrelated error types. Ignored
396 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
397 return;
398 }
399 // Clear the timer
400 fwAvailableTimer = nullptr;
Myung Bae8549b952023-08-16 15:18:19 -0400401}
402
403inline void
404 afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
405 const std::string& url, sdbusplus::message_t& m)
406{
Michael Shen80f79a42023-08-24 13:41:53 +0000407 dbus::utility::DBusInterfacesMap interfacesProperties;
Myung Bae8549b952023-08-16 15:18:19 -0400408 sdbusplus::message::object_path objPath;
409 m.read(objPath, interfacesProperties);
410 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
411 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
412 interface : interfacesProperties)
413 {
414 if (interface.first == "xyz.openbmc_project.Logging.Entry")
415 {
416 for (const std::pair<std::string, dbus::utility::DbusVariantType>&
417 value : interface.second)
418 {
419 if (value.first != "Message")
420 {
421 continue;
422 }
423 const std::string* type =
424 std::get_if<std::string>(&value.second);
425 if (type == nullptr)
426 {
427 // if this was our message, timeout will cover it
428 return;
429 }
Myung Bae8549b952023-08-16 15:18:19 -0400430 handleUpdateErrorType(asyncResp, url, *type);
431 }
432 }
433 }
434}
435
Andrew Geissler0554c982019-04-23 14:40:12 -0500436// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
437// then no asyncResp updates will occur
Ed Tanousf5139332024-04-03 13:25:04 -0700438inline void monitorForSoftwareAvailable(
zhanghch058d1b46d2021-04-01 11:18:24 +0800439 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
440 const crow::Request& req, const std::string& url,
Gunnar Mills5d138942022-09-07 10:26:21 -0500441 int timeoutTimeSeconds = 25)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500442{
443 // Only allow one FW update at a time
Ed Tanouse05aec52022-01-25 10:28:56 -0800444 if (fwUpdateInProgress)
Andrew Geissler86adcd62019-04-18 10:58:05 -0500445 {
Andrew Geissler0554c982019-04-23 14:40:12 -0500446 if (asyncResp)
447 {
Andrew Geissler0554c982019-04-23 14:40:12 -0500448 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
449 }
Andrew Geissler86adcd62019-04-18 10:58:05 -0500450 return;
451 }
452
Ed Tanous8e8245d2024-04-11 22:21:38 -0700453 if (req.ioService == nullptr)
454 {
455 messages::internalError(asyncResp->res);
456 return;
457 }
458
Andrew Geissler0554c982019-04-23 14:40:12 -0500459 fwAvailableTimer =
Ed Tanous271584a2019-07-09 16:24:22 -0700460 std::make_unique<boost::asio::steady_timer>(*req.ioService);
Andrew Geissler86adcd62019-04-18 10:58:05 -0500461
Ed Tanous271584a2019-07-09 16:24:22 -0700462 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500463
464 fwAvailableTimer->async_wait(
Myung Bae8549b952023-08-16 15:18:19 -0400465 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
466
Ed Tanousa3e65892021-09-16 14:13:20 -0700467 task::Payload payload(req);
Patrick Williams59d494e2022-07-22 19:26:55 -0500468 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
Ed Tanous62598e32023-07-17 17:06:25 -0700469 BMCWEB_LOG_DEBUG("Match fired");
Ed Tanousa3e65892021-09-16 14:13:20 -0700470 softwareInterfaceAdded(asyncResp, m, std::move(payload));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500471 };
472
473 fwUpdateInProgress = true;
474
Patrick Williams59d494e2022-07-22 19:26:55 -0500475 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
Andrew Geissler86adcd62019-04-18 10:58:05 -0500476 *crow::connections::systemBus,
477 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
478 "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
479 callback);
James Feist4cde5d92020-06-11 10:39:55 -0700480
Patrick Williams59d494e2022-07-22 19:26:55 -0500481 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
James Feist4cde5d92020-06-11 10:39:55 -0700482 *crow::connections::systemBus,
Brian Mae1cc4822021-12-01 17:05:54 +0800483 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
484 "member='InterfacesAdded',"
485 "path='/xyz/openbmc_project/logging'",
Myung Bae8549b952023-08-16 15:18:19 -0400486 std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
Andrew Geissler86adcd62019-04-18 10:58:05 -0500487}
Jennifer Lee729dae72018-04-24 15:59:34 -0700488
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400489inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
490 std::string imageURI, std::optional<std::string> transferProtocol,
491 crow::Response& res)
Ed Tanousf86bcc82023-08-25 09:34:07 -0700492{
493 if (imageURI.find("://") == std::string::npos)
494 {
495 if (imageURI.starts_with("/"))
496 {
497 messages::actionParameterValueTypeError(
498 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
499 return std::nullopt;
500 }
501 if (!transferProtocol)
502 {
503 messages::actionParameterValueTypeError(
504 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
505 return std::nullopt;
506 }
Ed Tanouse5cf7772024-04-03 13:45:31 -0700507 // OpenBMC currently only supports TFTP or HTTPS
Ed Tanous757178a2024-04-03 14:32:38 -0700508 if (*transferProtocol == "TFTP")
509 {
510 imageURI = "tftp://" + imageURI;
511 }
Ed Tanouse5cf7772024-04-03 13:45:31 -0700512 else if (*transferProtocol == "HTTPS")
513 {
514 imageURI = "https://" + imageURI;
515 }
Ed Tanous757178a2024-04-03 14:32:38 -0700516 else
Ed Tanousf86bcc82023-08-25 09:34:07 -0700517 {
518 messages::actionParameterNotSupported(res, "TransferProtocol",
519 *transferProtocol);
520 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
521 *transferProtocol);
522 return std::nullopt;
523 }
Ed Tanousf86bcc82023-08-25 09:34:07 -0700524 }
525
526 boost::system::result<boost::urls::url> url =
527 boost::urls::parse_absolute_uri(imageURI);
528 if (!url)
529 {
530 messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
531 "UpdateService.SimpleUpdate");
532
533 return std::nullopt;
534 }
535 url->normalize();
536
Ed Tanous757178a2024-04-03 14:32:38 -0700537 if (url->scheme() == "tftp")
538 {
539 if (url->encoded_path().size() < 2)
540 {
541 messages::actionParameterNotSupported(res, "ImageURI",
542 url->buffer());
543 return std::nullopt;
544 }
545 }
Ed Tanouse5cf7772024-04-03 13:45:31 -0700546 else if (url->scheme() == "https")
547 {
548 // Empty paths default to "/"
549 if (url->encoded_path().empty())
550 {
551 url->set_encoded_path("/");
552 }
553 }
Ed Tanous757178a2024-04-03 14:32:38 -0700554 else
Ed Tanousf86bcc82023-08-25 09:34:07 -0700555 {
556 messages::actionParameterNotSupported(res, "ImageURI", imageURI);
557 return std::nullopt;
558 }
Ed Tanous757178a2024-04-03 14:32:38 -0700559
560 if (url->encoded_path().empty())
Ed Tanousf86bcc82023-08-25 09:34:07 -0700561 {
Ed Tanous757178a2024-04-03 14:32:38 -0700562 messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
563 "UpdateService.SimpleUpdate");
Ed Tanousf86bcc82023-08-25 09:34:07 -0700564 return std::nullopt;
565 }
Ed Tanous757178a2024-04-03 14:32:38 -0700566
567 return *url;
Ed Tanousf86bcc82023-08-25 09:34:07 -0700568}
569
Ed Tanouse5cf7772024-04-03 13:45:31 -0700570inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
571 const boost::urls::url_view_base& url)
572{
573 messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
574 url.buffer());
575}
576
Ed Tanousf5139332024-04-03 13:25:04 -0700577inline void handleUpdateServiceSimpleUpdateAction(
578 crow::App& app, const crow::Request& req,
579 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
Andrew Geissler0554c982019-04-23 14:40:12 -0500580{
Ed Tanousf5139332024-04-03 13:25:04 -0700581 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
582 {
583 return;
584 }
585
586 std::optional<std::string> transferProtocol;
587 std::string imageURI;
588
589 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
590
591 // User can pass in both TransferProtocol and ImageURI parameters or
592 // they can pass in just the ImageURI with the transfer protocol
593 // embedded within it.
594 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
595 // 2) ImageURI:tftp://1.1.1.1/myfile.bin
596
597 if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
598 transferProtocol, "ImageURI", imageURI))
599 {
600 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
601 return;
602 }
603
Ed Tanous757178a2024-04-03 14:32:38 -0700604 std::optional<boost::urls::url> url =
605 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
606 if (!url)
Ed Tanousf5139332024-04-03 13:25:04 -0700607 {
608 return;
609 }
Jagpal Singh Gill4e338b22024-06-14 14:24:56 -0700610 if (url->scheme() == "https")
Ed Tanouse5cf7772024-04-03 13:45:31 -0700611 {
612 doHttpsUpdate(asyncResp, *url);
613 }
Ed Tanous757178a2024-04-03 14:32:38 -0700614 else
615 {
616 messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
617 url->buffer());
618 return;
619 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700620
Ed Tanousf5139332024-04-03 13:25:04 -0700621 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700622}
623
George Liu0ed80c82020-05-12 16:06:27 +0800624inline void uploadImageFile(crow::Response& res, std::string_view body)
625{
Ed Tanous2c6ffdb2023-06-28 11:28:38 -0700626 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
627
Ed Tanous62598e32023-07-17 17:06:25 -0700628 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
George Liu0ed80c82020-05-12 16:06:27 +0800629 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
630 std::ofstream::trunc);
631 // set the permission of the file to 640
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400632 std::filesystem::perms permission =
633 std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
George Liu0ed80c82020-05-12 16:06:27 +0800634 std::filesystem::permissions(filepath, permission);
635 out << body;
636
637 if (out.bad())
638 {
639 messages::internalError(res);
640 cleanUp();
641 }
642}
643
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700644// Convert the Request Apply Time to the D-Bus value
645inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
646 std::string& applyTimeNewVal)
647{
648 if (applyTime == "Immediate")
649 {
650 applyTimeNewVal =
Jagpal Singh Gill049079f2024-06-02 18:11:13 -0700651 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700652 }
653 else if (applyTime == "OnReset")
654 {
655 applyTimeNewVal =
Jagpal Singh Gill049079f2024-06-02 18:11:13 -0700656 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700657 }
658 else
659 {
660 BMCWEB_LOG_WARNING(
661 "ApplyTime value {} is not in the list of acceptable values",
662 applyTime);
663 messages::propertyValueNotInList(res, applyTime, "ApplyTime");
664 return false;
665 }
666 return true;
667}
668
George Liu0ed80c82020-05-12 16:06:27 +0800669inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
670 const std::string& applyTime)
671{
672 std::string applyTimeNewVal;
Jagpal Singh Gill049079f2024-06-02 18:11:13 -0700673 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
George Liu0ed80c82020-05-12 16:06:27 +0800674 {
George Liu0ed80c82020-05-12 16:06:27 +0800675 return;
676 }
677
Ginu Georgee93abac2024-06-14 17:35:27 +0530678 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
Ed Tanousd02aad32024-02-13 14:43:34 -0800679 sdbusplus::message::object_path(
680 "/xyz/openbmc_project/software/apply_time"),
681 "xyz.openbmc_project.Software.ApplyTime",
Ginu Georgee93abac2024-06-14 17:35:27 +0530682 "RequestedApplyTime", applyTimeNewVal);
George Liu0ed80c82020-05-12 16:06:27 +0800683}
684
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700685struct MultiPartUpdateParameters
George Liu0ed80c82020-05-12 16:06:27 +0800686{
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700687 std::optional<std::string> applyTime;
688 std::string uploadData;
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700689 std::vector<std::string> targets;
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700690};
691
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700692inline std::optional<std::string>
693 processUrl(boost::system::result<boost::urls::url_view>& url)
694{
695 if (!url)
696 {
697 return std::nullopt;
698 }
699 if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
700 BMCWEB_REDFISH_MANAGER_URI_NAME))
701 {
702 return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
703 }
704 if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
705 {
706 return std::nullopt;
707 }
708 std::string firmwareId;
709 if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
710 "FirmwareInventory",
711 std::ref(firmwareId)))
712 {
713 return std::nullopt;
714 }
715
716 return std::make_optional(firmwareId);
717}
718
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700719inline std::optional<MultiPartUpdateParameters>
720 extractMultipartUpdateParameters(
721 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
722 MultipartParser parser)
723{
724 MultiPartUpdateParameters multiRet;
725 for (FormPart& formpart : parser.mime_fields)
George Liu0ed80c82020-05-12 16:06:27 +0800726 {
727 boost::beast::http::fields::const_iterator it =
728 formpart.fields.find("Content-Disposition");
729 if (it == formpart.fields.end())
730 {
Ed Tanous62598e32023-07-17 17:06:25 -0700731 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700732 return std::nullopt;
George Liu0ed80c82020-05-12 16:06:27 +0800733 }
Ed Tanous62598e32023-07-17 17:06:25 -0700734 BMCWEB_LOG_INFO("Parsing value {}", it->value());
George Liu0ed80c82020-05-12 16:06:27 +0800735
736 // The construction parameters of param_list must start with `;`
737 size_t index = it->value().find(';');
738 if (index == std::string::npos)
739 {
740 continue;
741 }
742
Patrick Williams89492a12023-05-10 07:51:34 -0500743 for (const auto& param :
George Liu0ed80c82020-05-12 16:06:27 +0800744 boost::beast::http::param_list{it->value().substr(index)})
745 {
746 if (param.first != "name" || param.second.empty())
747 {
748 continue;
749 }
750
751 if (param.second == "UpdateParameters")
752 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700753 std::vector<std::string> tempTargets;
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400754 nlohmann::json content =
755 nlohmann::json::parse(formpart.content, nullptr, false);
Ed Tanousac1e1242024-07-10 22:10:14 -0700756 if (content.is_discarded())
757 {
758 return std::nullopt;
759 }
Ed Tanous7cb59f62022-05-05 11:48:31 -0700760 nlohmann::json::object_t* obj =
761 content.get_ptr<nlohmann::json::object_t*>();
762 if (obj == nullptr)
763 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700764 messages::propertyValueTypeError(
765 asyncResp->res, formpart.content, "UpdateParameters");
766 return std::nullopt;
Ed Tanous7cb59f62022-05-05 11:48:31 -0700767 }
768
769 if (!json_util::readJsonObject(
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700770 *obj, asyncResp->res, "Targets", tempTargets,
771 "@Redfish.OperationApplyTime", multiRet.applyTime))
George Liu0ed80c82020-05-12 16:06:27 +0800772 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700773 return std::nullopt;
George Liu0ed80c82020-05-12 16:06:27 +0800774 }
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700775
776 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
777 urlIndex++)
George Liu0ed80c82020-05-12 16:06:27 +0800778 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700779 const std::string& target = tempTargets[urlIndex];
780 boost::system::result<boost::urls::url_view> url =
781 boost::urls::parse_origin_form(target);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700782 auto res = processUrl(url);
783 if (!res.has_value())
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700784 {
785 messages::propertyValueFormatError(
786 asyncResp->res, target,
787 std::format("Targets/{}", urlIndex));
788 return std::nullopt;
789 }
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700790 multiRet.targets.emplace_back(res.value());
George Liu0ed80c82020-05-12 16:06:27 +0800791 }
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700792 if (multiRet.targets.size() != 1)
George Liu0ed80c82020-05-12 16:06:27 +0800793 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700794 messages::propertyValueFormatError(
795 asyncResp->res, multiRet.targets, "Targets");
796 return std::nullopt;
George Liu0ed80c82020-05-12 16:06:27 +0800797 }
George Liu0ed80c82020-05-12 16:06:27 +0800798 }
799 else if (param.second == "UpdateFile")
800 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700801 multiRet.uploadData = std::move(formpart.content);
George Liu0ed80c82020-05-12 16:06:27 +0800802 }
803 }
804 }
805
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700806 if (multiRet.uploadData.empty())
George Liu0ed80c82020-05-12 16:06:27 +0800807 {
Ed Tanous62598e32023-07-17 17:06:25 -0700808 BMCWEB_LOG_ERROR("Upload data is NULL");
George Liu0ed80c82020-05-12 16:06:27 +0800809 messages::propertyMissing(asyncResp->res, "UpdateFile");
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700810 return std::nullopt;
811 }
812 if (multiRet.targets.empty())
813 {
814 messages::propertyMissing(asyncResp->res, "Targets");
815 return std::nullopt;
816 }
817 return multiRet;
818}
819
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400820inline void handleStartUpdate(
821 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
822 const std::string& objectPath, const boost::system::error_code& ec,
823 const sdbusplus::message::object_path& retPath)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700824{
825 if (ec)
826 {
827 BMCWEB_LOG_ERROR("error_code = {}", ec);
828 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
829 messages::internalError(asyncResp->res);
830 return;
831 }
832
Jagpal Singh Gill587090c2024-08-12 00:24:16 -0700833 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
834 objectPath, retPath.str);
835 createTask(asyncResp, std::move(payload), retPath);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700836}
837
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400838inline void startUpdate(
839 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
840 const MemoryFileDescriptor& memfd, const std::string& applyTime,
841 const std::string& objectPath, const std::string& serviceName)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700842{
843 crow::connections::systemBus->async_method_call(
844 [asyncResp, payload = std::move(payload),
845 objectPath](const boost::system::error_code& ec1,
846 const sdbusplus::message::object_path& retPath) mutable {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400847 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
848 retPath);
849 },
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700850 serviceName, objectPath, "xyz.openbmc_project.Software.Update",
851 "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
852}
853
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700854inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
855 task::Payload payload, const MemoryFileDescriptor& memfd,
856 const std::string& applyTime, const std::string& target,
857 const boost::system::error_code& ec,
858 const dbus::utility::MapperGetSubTreeResponse& subtree)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700859{
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700860 using SwInfoMap = std::unordered_map<
861 std::string, std::pair<sdbusplus::message::object_path, std::string>>;
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700862 SwInfoMap swInfoMap;
863
864 if (ec)
865 {
866 BMCWEB_LOG_ERROR("error_code = {}", ec);
867 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
868 messages::internalError(asyncResp->res);
869 return;
870 }
871 BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
872
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700873 for (const auto& entry : subtree)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700874 {
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700875 sdbusplus::message::object_path path(entry.first);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700876 std::string swId = path.filename();
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700877 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700878 }
879
880 auto swEntry = swInfoMap.find(target);
881 if (swEntry == swInfoMap.end())
882 {
883 BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
884 messages::propertyValueFormatError(asyncResp->res, target, "Targets");
885 return;
886 }
887
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700888 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
889 swEntry->second.first.str, swEntry->second.second);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700890
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700891 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
892 swEntry->second.first.str, swEntry->second.second);
893}
894
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400895inline void handleBMCUpdate(
896 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
897 const MemoryFileDescriptor& memfd, const std::string& applyTime,
898 const boost::system::error_code& ec,
899 const dbus::utility::MapperEndPoints& functionalSoftware)
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700900{
901 if (ec)
902 {
903 BMCWEB_LOG_ERROR("error_code = {}", ec);
904 BMCWEB_LOG_ERROR("error msg = {}", ec.message());
905 messages::internalError(asyncResp->res);
906 return;
907 }
908 if (functionalSoftware.size() != 1)
909 {
910 BMCWEB_LOG_ERROR("Found {} functional software endpoints",
911 functionalSoftware.size());
912 messages::internalError(asyncResp->res);
913 return;
914 }
915
916 startUpdate(asyncResp, std::move(payload), memfd, applyTime,
917 functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700918}
919
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400920inline void processUpdateRequest(
921 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
922 task::Payload&& payload, std::string_view body,
923 const std::string& applyTime, std::vector<std::string>& targets)
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700924{
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700925 MemoryFileDescriptor memfd("update-image");
926 if (memfd.fd == -1)
927 {
928 BMCWEB_LOG_ERROR("Failed to create image memfd");
929 messages::internalError(asyncResp->res);
930 return;
931 }
932 if (write(memfd.fd, body.data(), body.length()) !=
933 static_cast<ssize_t>(body.length()))
934 {
935 BMCWEB_LOG_ERROR("Failed to write to image memfd");
936 messages::internalError(asyncResp->res);
937 return;
938 }
939 if (!memfd.rewind())
940 {
941 messages::internalError(asyncResp->res);
942 return;
943 }
944
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700945 if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
946 {
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700947 dbus::utility::getAssociationEndPoints(
Jagpal Singh Gill89449bb2024-08-12 16:17:58 -0700948 "/xyz/openbmc_project/software/bmc/updateable",
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700949 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
950 applyTime](
951 const boost::system::error_code& ec,
952 const dbus::utility::MapperEndPoints& objectPaths) mutable {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400953 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
954 ec, objectPaths);
955 });
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700956 }
957 else
958 {
959 constexpr std::array<std::string_view, 1> interfaces = {
960 "xyz.openbmc_project.Software.Version"};
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700961 dbus::utility::getSubTree(
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700962 "/xyz/openbmc_project/software", 1, interfaces,
963 [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
Jagpal Singh Gill08f61d52024-07-17 15:17:22 -0700964 applyTime, targets](const boost::system::error_code& ec,
965 const dbus::utility::MapperGetSubTreeResponse&
966 subtree) mutable {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400967 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
968 targets[0], ec, subtree);
969 });
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700970 }
971}
972
973inline void
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700974 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
975 const crow::Request& req, MultipartParser&& parser)
976{
977 std::optional<MultiPartUpdateParameters> multipart =
978 extractMultipartUpdateParameters(asyncResp, std::move(parser));
979 if (!multipart)
980 {
George Liu0ed80c82020-05-12 16:06:27 +0800981 return;
982 }
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700983 if (!multipart->applyTime)
George Liu0ed80c82020-05-12 16:06:27 +0800984 {
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -0700985 multipart->applyTime = "OnReset";
George Liu0ed80c82020-05-12 16:06:27 +0800986 }
987
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -0700988 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
989 {
Jagpal Singh Gill9dae4de2024-06-02 23:43:56 -0700990 std::string applyTimeNewVal;
991 if (!convertApplyTime(asyncResp->res, *multipart->applyTime,
992 applyTimeNewVal))
993 {
994 return;
995 }
996 task::Payload payload(req);
997
998 processUpdateRequest(asyncResp, std::move(payload),
999 multipart->uploadData, applyTimeNewVal,
1000 multipart->targets);
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -07001001 }
1002 else
1003 {
1004 setApplyTime(asyncResp, *multipart->applyTime);
George Liu0ed80c82020-05-12 16:06:27 +08001005
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -07001006 // Setup callback for when new software detected
1007 monitorForSoftwareAvailable(asyncResp, req,
1008 "/redfish/v1/UpdateService");
Ed Tanous6b54e4e2024-04-10 08:58:48 -07001009
Jagpal Singh Gillde0c9602024-04-29 17:30:21 -07001010 uploadImageFile(asyncResp->res, multipart->uploadData);
1011 }
George Liu0ed80c82020-05-12 16:06:27 +08001012}
1013
Jagpal Singh Gill9dae4de2024-06-02 23:43:56 -07001014inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1015 const crow::Request& req)
1016{
1017 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
1018 {
1019 task::Payload payload(req);
1020 // HTTP push only supports BMC updates (with ApplyTime as immediate) for
1021 // backwards compatibility. Specific component updates will be handled
1022 // through Multipart form HTTP push.
1023 std::vector<std::string> targets;
1024 targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME);
1025
1026 processUpdateRequest(
1027 asyncResp, std::move(payload), req.body(),
1028 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate",
1029 targets);
1030 }
1031 else
1032 {
1033 // Setup callback for when new software detected
1034 monitorForSoftwareAvailable(asyncResp, req,
1035 "/redfish/v1/UpdateService");
1036
1037 uploadImageFile(asyncResp->res, req.body());
1038 }
1039}
1040
Ed Tanousc2051d12022-05-11 12:21:55 -07001041inline void
1042 handleUpdateServicePost(App& app, const crow::Request& req,
1043 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1044{
Carson Labrado3ba00072022-06-06 19:40:56 +00001045 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
Ed Tanousc2051d12022-05-11 12:21:55 -07001046 {
1047 return;
1048 }
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001049 std::string_view contentType = req.getHeaderValue("Content-Type");
Ed Tanousc2051d12022-05-11 12:21:55 -07001050
Ed Tanous62598e32023-07-17 17:06:25 -07001051 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
Ed Tanousc2051d12022-05-11 12:21:55 -07001052
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001053 // Make sure that content type is application/octet-stream or
1054 // multipart/form-data
Ed Tanous18f8f602023-07-18 10:07:23 -07001055 if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
George Liu0ed80c82020-05-12 16:06:27 +08001056 {
Jagpal Singh Gill9dae4de2024-06-02 23:43:56 -07001057 doHTTPUpdate(asyncResp, req);
George Liu0ed80c82020-05-12 16:06:27 +08001058 }
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001059 else if (contentType.starts_with("multipart/form-data"))
George Liu0ed80c82020-05-12 16:06:27 +08001060 {
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001061 MultipartParser parser;
1062
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001063 ParserError ec = parser.parse(req);
1064 if (ec != ParserError::PARSER_SUCCESS)
1065 {
1066 // handle error
Ed Tanous62598e32023-07-17 17:06:25 -07001067 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1068 static_cast<int>(ec));
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001069 messages::internalError(asyncResp->res);
1070 return;
1071 }
Ed Tanous6b54e4e2024-04-10 08:58:48 -07001072
Jagpal Singh Gillef93eab2024-04-17 16:06:14 -07001073 updateMultipartContext(asyncResp, req, std::move(parser));
George Liu0ed80c82020-05-12 16:06:27 +08001074 }
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001075 else
1076 {
Ed Tanous62598e32023-07-17 17:06:25 -07001077 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
Ninad Palsuleb33a4322023-06-09 09:19:18 -05001078 asyncResp->res.result(boost::beast::http::status::bad_request);
1079 }
Ed Tanousc2051d12022-05-11 12:21:55 -07001080}
1081
Ed Tanousf5139332024-04-03 13:25:04 -07001082inline void
1083 handleUpdateServiceGet(App& app, const crow::Request& req,
1084 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001085{
Ed Tanousf5139332024-04-03 13:25:04 -07001086 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1087 {
1088 return;
1089 }
1090 asyncResp->res.jsonValue["@odata.type"] =
1091 "#UpdateService.v1_11_1.UpdateService";
1092 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
1093 asyncResp->res.jsonValue["Id"] = "UpdateService";
1094 asyncResp->res.jsonValue["Description"] = "Service for Software Update";
1095 asyncResp->res.jsonValue["Name"] = "Update Service";
Ed Tanous4dc23f32022-05-11 11:32:19 -07001096
Ed Tanousf5139332024-04-03 13:25:04 -07001097 asyncResp->res.jsonValue["HttpPushUri"] =
1098 "/redfish/v1/UpdateService/update";
1099 asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1100 "/redfish/v1/UpdateService/update";
Ed Tanous4dc23f32022-05-11 11:32:19 -07001101
Ed Tanousf5139332024-04-03 13:25:04 -07001102 // UpdateService cannot be disabled
1103 asyncResp->res.jsonValue["ServiceEnabled"] = true;
1104 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
1105 "/redfish/v1/UpdateService/FirmwareInventory";
1106 // Get the MaxImageSizeBytes
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001107 asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1108 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
Tejas Patild61e5192021-06-04 15:49:35 +05301109
Ed Tanousf5139332024-04-03 13:25:04 -07001110 // Update Actions object.
1111 nlohmann::json& updateSvcSimpleUpdate =
1112 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1113 updateSvcSimpleUpdate["target"] =
1114 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
Ed Tanous757178a2024-04-03 14:32:38 -07001115
1116 nlohmann::json::array_t allowed;
Ed Tanouse5cf7772024-04-03 13:45:31 -07001117 allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
Ed Tanous757178a2024-04-03 14:32:38 -07001118
Ed Tanous25b54db2024-04-17 15:40:31 -07001119 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
1120 {
1121 allowed.emplace_back(update_service::TransferProtocolType::TFTP);
1122 }
Ed Tanous757178a2024-04-03 14:32:38 -07001123
1124 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1125 std::move(allowed);
1126
Ed Tanous539d8c62024-06-19 14:38:27 -07001127 asyncResp->res
1128 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1129 update_service::ApplyTime::Immediate;
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001130}
Ed Tanousf5139332024-04-03 13:25:04 -07001131
1132inline void handleUpdateServiceFirmwareInventoryCollectionGet(
1133 App& app, const crow::Request& req,
1134 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1135{
1136 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1137 {
1138 return;
1139 }
1140 asyncResp->res.jsonValue["@odata.type"] =
1141 "#SoftwareInventoryCollection.SoftwareInventoryCollection";
1142 asyncResp->res.jsonValue["@odata.id"] =
1143 "/redfish/v1/UpdateService/FirmwareInventory";
1144 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
1145 const std::array<const std::string_view, 1> iface = {
1146 "xyz.openbmc_project.Software.Version"};
1147
1148 redfish::collection_util::getCollectionMembers(
1149 asyncResp,
1150 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
1151 "/xyz/openbmc_project/software");
1152}
1153
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001154/* Fill related item links (i.e. bmc, bios) in for inventory */
Ed Tanousf5139332024-04-03 13:25:04 -07001155inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1156 const std::string& purpose)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001157{
Willy Tueee00132022-06-14 14:53:17 -07001158 if (purpose == sw_util::bmcPurpose)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001159 {
Ed Tanousac106bf2023-06-07 09:24:59 -07001160 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
Ed Tanous14766872022-03-15 10:44:42 -07001161 nlohmann::json::object_t item;
Ed Tanous253f11b2024-05-16 09:38:31 -07001162 item["@odata.id"] = boost::urls::format(
1163 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
Patrick Williamsb2ba3072023-05-12 10:27:39 -05001164 relatedItem.emplace_back(std::move(item));
Ed Tanousac106bf2023-06-07 09:24:59 -07001165 asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1166 relatedItem.size();
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001167 }
Willy Tueee00132022-06-14 14:53:17 -07001168 else if (purpose == sw_util::biosPurpose)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001169 {
Ed Tanousac106bf2023-06-07 09:24:59 -07001170 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
Ed Tanous14766872022-03-15 10:44:42 -07001171 nlohmann::json::object_t item;
Ed Tanous253f11b2024-05-16 09:38:31 -07001172 item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1173 BMCWEB_REDFISH_SYSTEM_URI_NAME);
Patrick Williamsb2ba3072023-05-12 10:27:39 -05001174 relatedItem.emplace_back(std::move(item));
Ed Tanousac106bf2023-06-07 09:24:59 -07001175 asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1176 relatedItem.size();
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001177 }
1178 else
1179 {
Carson Labradobf2dded2023-08-10 00:37:06 +00001180 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001181 }
1182}
1183
Willy Tuaf246602022-06-14 15:51:53 -07001184inline void
1185 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1186 const std::string& service, const std::string& path,
1187 const std::string& swId)
1188{
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001189 sdbusplus::asio::getAllProperties(
1190 *crow::connections::systemBus, service, path,
1191 "xyz.openbmc_project.Software.Version",
Willy Tuaf246602022-06-14 15:51:53 -07001192 [asyncResp,
Ed Tanous8b242752023-06-27 17:17:13 -07001193 swId](const boost::system::error_code& ec,
Willy Tuaf246602022-06-14 15:51:53 -07001194 const dbus::utility::DBusPropertiesMap& propertiesList) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001195 if (ec)
1196 {
1197 messages::internalError(asyncResp->res);
1198 return;
1199 }
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001200
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001201 const std::string* swInvPurpose = nullptr;
1202 const std::string* version = nullptr;
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001203
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001204 const bool success = sdbusplus::unpackPropertiesNoThrow(
1205 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1206 swInvPurpose, "Version", version);
Krzysztof Grobelnyd1bde9e2022-09-07 10:40:51 +02001207
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001208 if (!success)
1209 {
1210 messages::internalError(asyncResp->res);
1211 return;
1212 }
Willy Tuaf246602022-06-14 15:51:53 -07001213
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001214 if (swInvPurpose == nullptr)
1215 {
1216 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1217 messages::internalError(asyncResp->res);
1218 return;
1219 }
Willy Tuaf246602022-06-14 15:51:53 -07001220
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001221 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
Willy Tuaf246602022-06-14 15:51:53 -07001222
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001223 if (version == nullptr)
1224 {
1225 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
Willy Tuaf246602022-06-14 15:51:53 -07001226
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001227 messages::internalError(asyncResp->res);
Willy Tuaf246602022-06-14 15:51:53 -07001228
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001229 return;
1230 }
1231 asyncResp->res.jsonValue["Version"] = *version;
1232 asyncResp->res.jsonValue["Id"] = swId;
Willy Tuaf246602022-06-14 15:51:53 -07001233
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001234 // swInvPurpose is of format:
1235 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1236 // Translate this to "ABC image"
1237 size_t endDesc = swInvPurpose->rfind('.');
1238 if (endDesc == std::string::npos)
1239 {
1240 messages::internalError(asyncResp->res);
1241 return;
1242 }
1243 endDesc++;
1244 if (endDesc >= swInvPurpose->size())
1245 {
1246 messages::internalError(asyncResp->res);
1247 return;
1248 }
Willy Tuaf246602022-06-14 15:51:53 -07001249
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001250 std::string formatDesc = swInvPurpose->substr(endDesc);
1251 asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1252 getRelatedItems(asyncResp, *swInvPurpose);
1253 });
Willy Tuaf246602022-06-14 15:51:53 -07001254}
1255
Ed Tanousf5139332024-04-03 13:25:04 -07001256inline void handleUpdateServiceFirmwareInventoryGet(
1257 App& app, const crow::Request& req,
1258 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1259 const std::string& param)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001260{
Ed Tanousf5139332024-04-03 13:25:04 -07001261 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1262 {
1263 return;
1264 }
1265 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1266
1267 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1268 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1269
1270 constexpr std::array<std::string_view, 1> interfaces = {
1271 "xyz.openbmc_project.Software.Version"};
1272 dbus::utility::getSubTree(
1273 "/", 0, interfaces,
1274 [asyncResp,
1275 swId](const boost::system::error_code& ec,
1276 const dbus::utility::MapperGetSubTreeResponse& subtree) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001277 BMCWEB_LOG_DEBUG("doGet callback...");
1278 if (ec)
Ed Tanous45ca1b82022-03-25 13:07:27 -07001279 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001280 messages::internalError(asyncResp->res);
1281 return;
Ed Tanous45ca1b82022-03-25 13:07:27 -07001282 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001283
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001284 // Ensure we find our input swId, otherwise return an error
1285 bool found = false;
1286 for (const std::pair<std::string,
1287 std::vector<std::pair<
1288 std::string, std::vector<std::string>>>>&
1289 obj : subtree)
Ed Tanous002d39b2022-05-31 08:59:27 -07001290 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001291 if (!obj.first.ends_with(*swId))
1292 {
1293 continue;
1294 }
1295
1296 if (obj.second.empty())
1297 {
1298 continue;
1299 }
1300
1301 found = true;
1302 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1303 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1304 *swId);
Ed Tanous002d39b2022-05-31 08:59:27 -07001305 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001306 if (!found)
1307 {
1308 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1309 messages::resourceMissingAtURI(
1310 asyncResp->res,
1311 boost::urls::format(
1312 "/redfish/v1/UpdateService/FirmwareInventory/{}",
1313 *swId));
1314 return;
1315 }
1316 asyncResp->res.jsonValue["@odata.type"] =
1317 "#SoftwareInventory.v1_1_0.SoftwareInventory";
1318 asyncResp->res.jsonValue["Name"] = "Software Inventory";
1319 asyncResp->res.jsonValue["Status"]["HealthRollup"] =
1320 resource::Health::OK;
Ed Tanous1abe55e2018-09-05 08:30:59 -07001321
Patrick Williamsbd79bce2024-08-16 15:22:20 -04001322 asyncResp->res.jsonValue["Updateable"] = false;
1323 sw_util::getSwUpdatableStatus(asyncResp, swId);
1324 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001325}
Ed Tanous1abe55e2018-09-05 08:30:59 -07001326
Ed Tanousf5139332024-04-03 13:25:04 -07001327inline void requestRoutesUpdateService(App& app)
1328{
1329 BMCWEB_ROUTE(
1330 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1331 .privileges(redfish::privileges::postUpdateService)
1332 .methods(boost::beast::http::verb::post)(std::bind_front(
1333 handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1334
1335 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1336 .privileges(redfish::privileges::getSoftwareInventory)
1337 .methods(boost::beast::http::verb::get)(std::bind_front(
1338 handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1339
1340 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1341 .privileges(redfish::privileges::getUpdateService)
1342 .methods(boost::beast::http::verb::get)(
1343 std::bind_front(handleUpdateServiceGet, std::ref(app)));
1344
Ed Tanousf5139332024-04-03 13:25:04 -07001345 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1346 .privileges(redfish::privileges::postUpdateService)
1347 .methods(boost::beast::http::verb::post)(
1348 std::bind_front(handleUpdateServicePost, std::ref(app)));
1349
1350 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1351 .privileges(redfish::privileges::getSoftwareInventoryCollection)
1352 .methods(boost::beast::http::verb::get)(std::bind_front(
1353 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1354}
1355
Ed Tanous1abe55e2018-09-05 08:30:59 -07001356} // namespace redfish