1 /*
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 
18 #include "bmcweb_config.h"
19 
20 #include "app.hpp"
21 #include "dbus_utility.hpp"
22 #include "error_messages.hpp"
23 #include "generated/enums/update_service.hpp"
24 #include "multipart_parser.hpp"
25 #include "ossl_random.hpp"
26 #include "query.hpp"
27 #include "registries/privilege_registry.hpp"
28 #include "task.hpp"
29 #include "task_messages.hpp"
30 #include "utils/collection.hpp"
31 #include "utils/dbus_utils.hpp"
32 #include "utils/json_utils.hpp"
33 #include "utils/sw_utils.hpp"
34 
35 #include <sys/mman.h>
36 
37 #include <boost/system/error_code.hpp>
38 #include <boost/url/format.hpp>
39 #include <sdbusplus/asio/property.hpp>
40 #include <sdbusplus/bus/match.hpp>
41 #include <sdbusplus/unpack_properties.hpp>
42 
43 #include <array>
44 #include <cstddef>
45 #include <filesystem>
46 #include <functional>
47 #include <iterator>
48 #include <memory>
49 #include <optional>
50 #include <string>
51 #include <string_view>
52 #include <unordered_map>
53 #include <vector>
54 
55 namespace redfish
56 {
57 
58 // Match signals added on software path
59 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
60 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
61 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
62 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
63 // Only allow one update at a time
64 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
65 static bool fwUpdateInProgress = false;
66 // Timer for software available
67 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
68 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
69 
70 struct 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 
105 inline void cleanUp()
106 {
107     fwUpdateInProgress = false;
108     fwUpdateMatcher = nullptr;
109     fwUpdateErrorMatcher = nullptr;
110 }
111 
112 inline void activateImage(const std::string& objPath,
113                           const std::string& service)
114 {
115     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
116     sdbusplus::asio::setProperty(
117         *crow::connections::systemBus, service, objPath,
118         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
119         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
120         [](const boost::system::error_code& ec) {
121             if (ec)
122             {
123                 BMCWEB_LOG_DEBUG("error_code = {}", ec);
124                 BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
125             }
126         });
127 }
128 
129 inline 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 
230 inline 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 
244 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
245 // then no asyncResp updates will occur
246 inline void
247     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
248                            sdbusplus::message_t& m, task::Payload&& payload)
249 {
250     dbus::utility::DBusInterfacesMap interfacesProperties;
251 
252     sdbusplus::message::object_path objPath;
253 
254     m.read(objPath, interfacesProperties);
255 
256     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
257     for (const auto& interface : interfacesProperties)
258     {
259         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
260 
261         if (interface.first == "xyz.openbmc_project.Software.Activation")
262         {
263             // Retrieve service and activate
264             constexpr std::array<std::string_view, 1> interfaces = {
265                 "xyz.openbmc_project.Software.Activation"};
266             dbus::utility::getDbusObject(
267                 objPath.str, interfaces,
268                 [objPath, asyncResp, payload(std::move(payload))](
269                     const boost::system::error_code& ec,
270                     const std::vector<
271                         std::pair<std::string, std::vector<std::string>>>&
272                         objInfo) mutable {
273                     if (ec)
274                     {
275                         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;
283                     }
284                     // Ensure we only got one service back
285                     if (objInfo.size() != 1)
286                     {
287                         BMCWEB_LOG_ERROR("Invalid Object Size {}",
288                                          objInfo.size());
289                         if (asyncResp)
290                         {
291                             messages::internalError(asyncResp->res);
292                         }
293                         cleanUp();
294                         return;
295                     }
296                     // cancel timer only when
297                     // xyz.openbmc_project.Software.Activation interface
298                     // is added
299                     fwAvailableTimer = nullptr;
300 
301                     activateImage(objPath.str, objInfo[0].first);
302                     if (asyncResp)
303                     {
304                         createTask(asyncResp, std::move(payload), objPath);
305                     }
306                     fwUpdateInProgress = false;
307                 });
308 
309             break;
310         }
311     }
312 }
313 
314 inline 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 
337 inline 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     }
369     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
370     {
371         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);
391         redfish::messages::internalError(asyncResp->res);
392     }
393     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;
401 }
402 
403 inline void
404     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
405                             const std::string& url, sdbusplus::message_t& m)
406 {
407     dbus::utility::DBusInterfacesMap interfacesProperties;
408     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                 }
430                 handleUpdateErrorType(asyncResp, url, *type);
431             }
432         }
433     }
434 }
435 
436 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
437 // then no asyncResp updates will occur
438 inline void monitorForSoftwareAvailable(
439     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
440     const crow::Request& req, const std::string& url,
441     int timeoutTimeSeconds = 25)
442 {
443     // Only allow one FW update at a time
444     if (fwUpdateInProgress)
445     {
446         if (asyncResp)
447         {
448             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
449         }
450         return;
451     }
452 
453     if (req.ioService == nullptr)
454     {
455         messages::internalError(asyncResp->res);
456         return;
457     }
458 
459     fwAvailableTimer =
460         std::make_unique<boost::asio::steady_timer>(*req.ioService);
461 
462     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
463 
464     fwAvailableTimer->async_wait(
465         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
466 
467     task::Payload payload(req);
468     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
469         BMCWEB_LOG_DEBUG("Match fired");
470         softwareInterfaceAdded(asyncResp, m, std::move(payload));
471     };
472 
473     fwUpdateInProgress = true;
474 
475     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
476         *crow::connections::systemBus,
477         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
478         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
479         callback);
480 
481     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
482         *crow::connections::systemBus,
483         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
484         "member='InterfacesAdded',"
485         "path='/xyz/openbmc_project/logging'",
486         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
487 }
488 
489 inline std::optional<boost::urls::url> parseSimpleUpdateUrl(
490     std::string imageURI, std::optional<std::string> transferProtocol,
491     crow::Response& res)
492 {
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         }
507         // OpenBMC currently only supports TFTP or HTTPS
508         if (*transferProtocol == "TFTP")
509         {
510             imageURI = "tftp://" + imageURI;
511         }
512         else if (*transferProtocol == "HTTPS")
513         {
514             imageURI = "https://" + imageURI;
515         }
516         else
517         {
518             messages::actionParameterNotSupported(res, "TransferProtocol",
519                                                   *transferProtocol);
520             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
521                              *transferProtocol);
522             return std::nullopt;
523         }
524     }
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 
537     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     }
546     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     }
554     else
555     {
556         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
557         return std::nullopt;
558     }
559 
560     if (url->encoded_path().empty())
561     {
562         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
563                                                 "UpdateService.SimpleUpdate");
564         return std::nullopt;
565     }
566 
567     return *url;
568 }
569 
570 inline 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 
577 inline void handleUpdateServiceSimpleUpdateAction(
578     crow::App& app, const crow::Request& req,
579     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
580 {
581     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 
604     std::optional<boost::urls::url> url =
605         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
606     if (!url)
607     {
608         return;
609     }
610     if (url->scheme() == "https")
611     {
612         doHttpsUpdate(asyncResp, *url);
613     }
614     else
615     {
616         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
617                                               url->buffer());
618         return;
619     }
620 
621     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
622 }
623 
624 inline void uploadImageFile(crow::Response& res, std::string_view body)
625 {
626     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
627 
628     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
629     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
630                                     std::ofstream::trunc);
631     // set the permission of the file to 640
632     std::filesystem::perms permission =
633         std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
634     std::filesystem::permissions(filepath, permission);
635     out << body;
636 
637     if (out.bad())
638     {
639         messages::internalError(res);
640         cleanUp();
641     }
642 }
643 
644 // Convert the Request Apply Time to the D-Bus value
645 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
646                              std::string& applyTimeNewVal)
647 {
648     if (applyTime == "Immediate")
649     {
650         applyTimeNewVal =
651             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
652     }
653     else if (applyTime == "OnReset")
654     {
655         applyTimeNewVal =
656             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
657     }
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 
669 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
670                          const std::string& applyTime)
671 {
672     std::string applyTimeNewVal;
673     if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
674     {
675         return;
676     }
677 
678     setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings",
679                     sdbusplus::message::object_path(
680                         "/xyz/openbmc_project/software/apply_time"),
681                     "xyz.openbmc_project.Software.ApplyTime",
682                     "RequestedApplyTime", applyTimeNewVal);
683 }
684 
685 struct MultiPartUpdateParameters
686 {
687     std::optional<std::string> applyTime;
688     std::string uploadData;
689     std::vector<std::string> targets;
690 };
691 
692 inline 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 
719 inline 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)
726     {
727         boost::beast::http::fields::const_iterator it =
728             formpart.fields.find("Content-Disposition");
729         if (it == formpart.fields.end())
730         {
731             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
732             return std::nullopt;
733         }
734         BMCWEB_LOG_INFO("Parsing value {}", it->value());
735 
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 
743         for (const auto& param :
744              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             {
753                 std::vector<std::string> tempTargets;
754                 nlohmann::json content =
755                     nlohmann::json::parse(formpart.content, nullptr, false);
756                 if (content.is_discarded())
757                 {
758                     return std::nullopt;
759                 }
760                 nlohmann::json::object_t* obj =
761                     content.get_ptr<nlohmann::json::object_t*>();
762                 if (obj == nullptr)
763                 {
764                     messages::propertyValueTypeError(
765                         asyncResp->res, formpart.content, "UpdateParameters");
766                     return std::nullopt;
767                 }
768 
769                 if (!json_util::readJsonObject(
770                         *obj, asyncResp->res, "Targets", tempTargets,
771                         "@Redfish.OperationApplyTime", multiRet.applyTime))
772                 {
773                     return std::nullopt;
774                 }
775 
776                 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
777                      urlIndex++)
778                 {
779                     const std::string& target = tempTargets[urlIndex];
780                     boost::system::result<boost::urls::url_view> url =
781                         boost::urls::parse_origin_form(target);
782                     auto res = processUrl(url);
783                     if (!res.has_value())
784                     {
785                         messages::propertyValueFormatError(
786                             asyncResp->res, target,
787                             std::format("Targets/{}", urlIndex));
788                         return std::nullopt;
789                     }
790                     multiRet.targets.emplace_back(res.value());
791                 }
792                 if (multiRet.targets.size() != 1)
793                 {
794                     messages::propertyValueFormatError(
795                         asyncResp->res, multiRet.targets, "Targets");
796                     return std::nullopt;
797                 }
798             }
799             else if (param.second == "UpdateFile")
800             {
801                 multiRet.uploadData = std::move(formpart.content);
802             }
803         }
804     }
805 
806     if (multiRet.uploadData.empty())
807     {
808         BMCWEB_LOG_ERROR("Upload data is NULL");
809         messages::propertyMissing(asyncResp->res, "UpdateFile");
810         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 
820 inline 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)
824 {
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 
833     BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}",
834                     objectPath, retPath.str);
835     createTask(asyncResp, std::move(payload), retPath);
836 }
837 
838 inline 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)
842 {
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 {
847             handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
848                               retPath);
849         },
850         serviceName, objectPath, "xyz.openbmc_project.Software.Update",
851         "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
852 }
853 
854 inline 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)
859 {
860     using SwInfoMap = std::unordered_map<
861         std::string, std::pair<sdbusplus::message::object_path, std::string>>;
862     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 
873     for (const auto& entry : subtree)
874     {
875         sdbusplus::message::object_path path(entry.first);
876         std::string swId = path.filename();
877         swInfoMap.emplace(swId, make_pair(path, entry.second[0].first));
878     }
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 
888     BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}",
889                      swEntry->second.first.str, swEntry->second.second);
890 
891     startUpdate(asyncResp, std::move(payload), memfd, applyTime,
892                 swEntry->second.first.str, swEntry->second.second);
893 }
894 
895 inline 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)
900 {
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");
918 }
919 
920 inline 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)
924 {
925     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 
945     if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
946     {
947         dbus::utility::getAssociationEndPoints(
948             "/xyz/openbmc_project/software/bmc/updateable",
949             [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 {
953                 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime,
954                                 ec, objectPaths);
955             });
956     }
957     else
958     {
959         constexpr std::array<std::string_view, 1> interfaces = {
960             "xyz.openbmc_project.Software.Version"};
961         dbus::utility::getSubTree(
962             "/xyz/openbmc_project/software", 1, interfaces,
963             [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
964              applyTime, targets](const boost::system::error_code& ec,
965                                  const dbus::utility::MapperGetSubTreeResponse&
966                                      subtree) mutable {
967                 getSwInfo(asyncResp, std::move(payload), memfd, applyTime,
968                           targets[0], ec, subtree);
969             });
970     }
971 }
972 
973 inline void
974     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     {
981         return;
982     }
983     if (!multipart->applyTime)
984     {
985         multipart->applyTime = "OnReset";
986     }
987 
988     if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
989     {
990         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);
1001     }
1002     else
1003     {
1004         setApplyTime(asyncResp, *multipart->applyTime);
1005 
1006         // Setup callback for when new software detected
1007         monitorForSoftwareAvailable(asyncResp, req,
1008                                     "/redfish/v1/UpdateService");
1009 
1010         uploadImageFile(asyncResp->res, multipart->uploadData);
1011     }
1012 }
1013 
1014 inline 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 
1041 inline void
1042     handleUpdateServicePost(App& app, const crow::Request& req,
1043                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1044 {
1045     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1046     {
1047         return;
1048     }
1049     std::string_view contentType = req.getHeaderValue("Content-Type");
1050 
1051     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
1052 
1053     // Make sure that content type is application/octet-stream or
1054     // multipart/form-data
1055     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
1056     {
1057         doHTTPUpdate(asyncResp, req);
1058     }
1059     else if (contentType.starts_with("multipart/form-data"))
1060     {
1061         MultipartParser parser;
1062 
1063         ParserError ec = parser.parse(req);
1064         if (ec != ParserError::PARSER_SUCCESS)
1065         {
1066             // handle error
1067             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
1068                              static_cast<int>(ec));
1069             messages::internalError(asyncResp->res);
1070             return;
1071         }
1072 
1073         updateMultipartContext(asyncResp, req, std::move(parser));
1074     }
1075     else
1076     {
1077         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
1078         asyncResp->res.result(boost::beast::http::status::bad_request);
1079     }
1080 }
1081 
1082 inline void
1083     handleUpdateServiceGet(App& app, const crow::Request& req,
1084                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1085 {
1086     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";
1096 
1097     asyncResp->res.jsonValue["HttpPushUri"] =
1098         "/redfish/v1/UpdateService/update";
1099     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
1100         "/redfish/v1/UpdateService/update";
1101 
1102     // 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
1107     asyncResp->res.jsonValue["MaxImageSizeBytes"] =
1108         BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024;
1109 
1110     // Update Actions object.
1111     nlohmann::json& updateSvcSimpleUpdate =
1112         asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
1113     updateSvcSimpleUpdate["target"] =
1114         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
1115 
1116     nlohmann::json::array_t allowed;
1117     allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
1118 
1119     if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
1120     {
1121         allowed.emplace_back(update_service::TransferProtocolType::TFTP);
1122     }
1123 
1124     updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
1125         std::move(allowed);
1126 
1127     asyncResp->res
1128         .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] =
1129         update_service::ApplyTime::Immediate;
1130 }
1131 
1132 inline 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 
1154 /* Fill related item links (i.e. bmc, bios) in for inventory */
1155 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1156                             const std::string& purpose)
1157 {
1158     if (purpose == sw_util::bmcPurpose)
1159     {
1160         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1161         nlohmann::json::object_t item;
1162         item["@odata.id"] = boost::urls::format(
1163             "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME);
1164         relatedItem.emplace_back(std::move(item));
1165         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1166             relatedItem.size();
1167     }
1168     else if (purpose == sw_util::biosPurpose)
1169     {
1170         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1171         nlohmann::json::object_t item;
1172         item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios",
1173                                         BMCWEB_REDFISH_SYSTEM_URI_NAME);
1174         relatedItem.emplace_back(std::move(item));
1175         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1176             relatedItem.size();
1177     }
1178     else
1179     {
1180         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1181     }
1182 }
1183 
1184 inline 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 {
1189     sdbusplus::asio::getAllProperties(
1190         *crow::connections::systemBus, service, path,
1191         "xyz.openbmc_project.Software.Version",
1192         [asyncResp,
1193          swId](const boost::system::error_code& ec,
1194                const dbus::utility::DBusPropertiesMap& propertiesList) {
1195             if (ec)
1196             {
1197                 messages::internalError(asyncResp->res);
1198                 return;
1199             }
1200 
1201             const std::string* swInvPurpose = nullptr;
1202             const std::string* version = nullptr;
1203 
1204             const bool success = sdbusplus::unpackPropertiesNoThrow(
1205                 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1206                 swInvPurpose, "Version", version);
1207 
1208             if (!success)
1209             {
1210                 messages::internalError(asyncResp->res);
1211                 return;
1212             }
1213 
1214             if (swInvPurpose == nullptr)
1215             {
1216                 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1217                 messages::internalError(asyncResp->res);
1218                 return;
1219             }
1220 
1221             BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1222 
1223             if (version == nullptr)
1224             {
1225                 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1226 
1227                 messages::internalError(asyncResp->res);
1228 
1229                 return;
1230             }
1231             asyncResp->res.jsonValue["Version"] = *version;
1232             asyncResp->res.jsonValue["Id"] = swId;
1233 
1234             // 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             }
1249 
1250             std::string formatDesc = swInvPurpose->substr(endDesc);
1251             asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1252             getRelatedItems(asyncResp, *swInvPurpose);
1253         });
1254 }
1255 
1256 inline void handleUpdateServiceFirmwareInventoryGet(
1257     App& app, const crow::Request& req,
1258     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1259     const std::string& param)
1260 {
1261     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) {
1277             BMCWEB_LOG_DEBUG("doGet callback...");
1278             if (ec)
1279             {
1280                 messages::internalError(asyncResp->res);
1281                 return;
1282             }
1283 
1284             // 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)
1290             {
1291                 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);
1305             }
1306             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;
1321 
1322             asyncResp->res.jsonValue["Updateable"] = false;
1323             sw_util::getSwUpdatableStatus(asyncResp, swId);
1324         });
1325 }
1326 
1327 inline 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 
1345     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 
1356 } // namespace redfish
1357