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 "generated/enums/update_service.hpp"
23 #include "multipart_parser.hpp"
24 #include "ossl_random.hpp"
25 #include "query.hpp"
26 #include "registries/privilege_registry.hpp"
27 #include "task.hpp"
28 #include "utils/collection.hpp"
29 #include "utils/dbus_utils.hpp"
30 #include "utils/sw_utils.hpp"
31 
32 #include <boost/system/error_code.hpp>
33 #include <boost/url/format.hpp>
34 #include <sdbusplus/asio/property.hpp>
35 #include <sdbusplus/bus/match.hpp>
36 #include <sdbusplus/unpack_properties.hpp>
37 
38 #include <array>
39 #include <filesystem>
40 #include <optional>
41 #include <string>
42 #include <string_view>
43 
44 namespace redfish
45 {
46 
47 // Match signals added on software path
48 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
49 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
50 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
51 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
52 // Only allow one update at a time
53 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
54 static bool fwUpdateInProgress = false;
55 // Timer for software available
56 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
57 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
58 
59 inline void cleanUp()
60 {
61     fwUpdateInProgress = false;
62     fwUpdateMatcher = nullptr;
63     fwUpdateErrorMatcher = nullptr;
64 }
65 
66 inline void activateImage(const std::string& objPath,
67                           const std::string& service)
68 {
69     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
70     sdbusplus::asio::setProperty(
71         *crow::connections::systemBus, service, objPath,
72         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
73         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
74         [](const boost::system::error_code& ec) {
75         if (ec)
76         {
77             BMCWEB_LOG_DEBUG("error_code = {}", ec);
78             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
79         }
80     });
81 }
82 
83 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
84 // then no asyncResp updates will occur
85 static void
86     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
87                            sdbusplus::message_t& m, task::Payload&& payload)
88 {
89     dbus::utility::DBusInterfacesMap interfacesProperties;
90 
91     sdbusplus::message::object_path objPath;
92 
93     m.read(objPath, interfacesProperties);
94 
95     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
96     for (const auto& interface : interfacesProperties)
97     {
98         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
99 
100         if (interface.first == "xyz.openbmc_project.Software.Activation")
101         {
102             // Retrieve service and activate
103             constexpr std::array<std::string_view, 1> interfaces = {
104                 "xyz.openbmc_project.Software.Activation"};
105             dbus::utility::getDbusObject(
106                 objPath.str, interfaces,
107                 [objPath, asyncResp, payload(std::move(payload))](
108                     const boost::system::error_code& ec,
109                     const std::vector<
110                         std::pair<std::string, std::vector<std::string>>>&
111                         objInfo) mutable {
112                 if (ec)
113                 {
114                     BMCWEB_LOG_DEBUG("error_code = {}", ec);
115                     BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
116                     if (asyncResp)
117                     {
118                         messages::internalError(asyncResp->res);
119                     }
120                     cleanUp();
121                     return;
122                 }
123                 // Ensure we only got one service back
124                 if (objInfo.size() != 1)
125                 {
126                     BMCWEB_LOG_ERROR("Invalid Object Size {}", objInfo.size());
127                     if (asyncResp)
128                     {
129                         messages::internalError(asyncResp->res);
130                     }
131                     cleanUp();
132                     return;
133                 }
134                 // cancel timer only when
135                 // xyz.openbmc_project.Software.Activation interface
136                 // is added
137                 fwAvailableTimer = nullptr;
138 
139                 activateImage(objPath.str, objInfo[0].first);
140                 if (asyncResp)
141                 {
142                     std::shared_ptr<task::TaskData> task =
143                         task::TaskData::createTask(
144                             [](const boost::system::error_code& ec2,
145                                sdbusplus::message_t& msg,
146                                const std::shared_ptr<task::TaskData>&
147                                    taskData) {
148                         if (ec2)
149                         {
150                             return task::completed;
151                         }
152 
153                         std::string iface;
154                         dbus::utility::DBusPropertiesMap values;
155 
156                         std::string index = std::to_string(taskData->index);
157                         msg.read(iface, values);
158 
159                         if (iface == "xyz.openbmc_project.Software.Activation")
160                         {
161                             const std::string* state = nullptr;
162                             for (const auto& property : values)
163                             {
164                                 if (property.first == "Activation")
165                                 {
166                                     state = std::get_if<std::string>(
167                                         &property.second);
168                                     if (state == nullptr)
169                                     {
170                                         taskData->messages.emplace_back(
171                                             messages::internalError());
172                                         return task::completed;
173                                     }
174                                 }
175                             }
176 
177                             if (state == nullptr)
178                             {
179                                 return !task::completed;
180                             }
181 
182                             if (state->ends_with("Invalid") ||
183                                 state->ends_with("Failed"))
184                             {
185                                 taskData->state = "Exception";
186                                 taskData->status = "Warning";
187                                 taskData->messages.emplace_back(
188                                     messages::taskAborted(index));
189                                 return task::completed;
190                             }
191 
192                             if (state->ends_with("Staged"))
193                             {
194                                 taskData->state = "Stopping";
195                                 taskData->messages.emplace_back(
196                                     messages::taskPaused(index));
197 
198                                 // its staged, set a long timer to
199                                 // allow them time to complete the
200                                 // update (probably cycle the
201                                 // system) if this expires then
202                                 // task will be canceled
203                                 taskData->extendTimer(std::chrono::hours(5));
204                                 return !task::completed;
205                             }
206 
207                             if (state->ends_with("Active"))
208                             {
209                                 taskData->messages.emplace_back(
210                                     messages::taskCompletedOK(index));
211                                 taskData->state = "Completed";
212                                 return task::completed;
213                             }
214                         }
215                         else if (
216                             iface ==
217                             "xyz.openbmc_project.Software.ActivationProgress")
218                         {
219                             const uint8_t* progress = nullptr;
220                             for (const auto& property : values)
221                             {
222                                 if (property.first == "Progress")
223                                 {
224                                     progress =
225                                         std::get_if<uint8_t>(&property.second);
226                                     if (progress == nullptr)
227                                     {
228                                         taskData->messages.emplace_back(
229                                             messages::internalError());
230                                         return task::completed;
231                                     }
232                                 }
233                             }
234 
235                             if (progress == nullptr)
236                             {
237                                 return !task::completed;
238                             }
239                             taskData->percentComplete = *progress;
240                             taskData->messages.emplace_back(
241                                 messages::taskProgressChanged(index,
242                                                               *progress));
243 
244                             // if we're getting status updates it's
245                             // still alive, update timer
246                             taskData->extendTimer(std::chrono::minutes(5));
247                         }
248 
249                         // as firmware update often results in a
250                         // reboot, the task  may never "complete"
251                         // unless it is an error
252 
253                         return !task::completed;
254                     },
255                             "type='signal',interface='org.freedesktop.DBus.Properties',"
256                             "member='PropertiesChanged',path='" +
257                                 objPath.str + "'");
258                     task->startTimer(std::chrono::minutes(5));
259                     task->populateResp(asyncResp->res);
260                     task->payload.emplace(std::move(payload));
261                 }
262                 fwUpdateInProgress = false;
263             });
264 
265             break;
266         }
267     }
268 }
269 
270 inline void afterAvailbleTimerAsyncWait(
271     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
272     const boost::system::error_code& ec)
273 {
274     cleanUp();
275     if (ec == boost::asio::error::operation_aborted)
276     {
277         // expected, we were canceled before the timer completed.
278         return;
279     }
280     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
281     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
282     if (ec)
283     {
284         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
285         return;
286     }
287     if (asyncResp)
288     {
289         redfish::messages::internalError(asyncResp->res);
290     }
291 }
292 
293 inline void
294     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
295                           const std::string& url, const std::string& type)
296 {
297     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
298     {
299         redfish::messages::invalidUpload(asyncResp->res, url,
300                                          "Invalid archive");
301     }
302     else if (type ==
303              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
304     {
305         redfish::messages::invalidUpload(asyncResp->res, url,
306                                          "Invalid manifest");
307     }
308     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
309     {
310         redfish::messages::invalidUpload(asyncResp->res, url,
311                                          "Invalid image format");
312     }
313     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
314     {
315         redfish::messages::invalidUpload(asyncResp->res, url,
316                                          "Image version already exists");
317 
318         redfish::messages::resourceAlreadyExists(
319             asyncResp->res, "UpdateService", "Version", "uploaded version");
320     }
321     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
322     {
323         redfish::messages::resourceExhaustion(asyncResp->res, url);
324     }
325     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
326     {
327         redfish::messages::invalidUpload(asyncResp->res, url,
328                                          "Incompatible image version");
329     }
330     else if (type ==
331              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
332     {
333         redfish::messages::invalidUpload(asyncResp->res, url,
334                                          "Update Access Key Expired");
335     }
336     else if (type ==
337              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
338     {
339         redfish::messages::invalidUpload(asyncResp->res, url,
340                                          "Invalid image signature");
341     }
342     else if (type ==
343                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
344              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
345     {
346         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
347         redfish::messages::internalError(asyncResp->res);
348     }
349     else
350     {
351         // Unrelated error types. Ignored
352         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
353         return;
354     }
355     // Clear the timer
356     fwAvailableTimer = nullptr;
357 }
358 
359 inline void
360     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
361                             const std::string& url, sdbusplus::message_t& m)
362 {
363     dbus::utility::DBusInterfacesMap interfacesProperties;
364     sdbusplus::message::object_path objPath;
365     m.read(objPath, interfacesProperties);
366     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
367     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
368              interface : interfacesProperties)
369     {
370         if (interface.first == "xyz.openbmc_project.Logging.Entry")
371         {
372             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
373                      value : interface.second)
374             {
375                 if (value.first != "Message")
376                 {
377                     continue;
378                 }
379                 const std::string* type =
380                     std::get_if<std::string>(&value.second);
381                 if (type == nullptr)
382                 {
383                     // if this was our message, timeout will cover it
384                     return;
385                 }
386                 handleUpdateErrorType(asyncResp, url, *type);
387             }
388         }
389     }
390 }
391 
392 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
393 // then no asyncResp updates will occur
394 inline void monitorForSoftwareAvailable(
395     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
396     const crow::Request& req, const std::string& url,
397     int timeoutTimeSeconds = 25)
398 {
399     // Only allow one FW update at a time
400     if (fwUpdateInProgress)
401     {
402         if (asyncResp)
403         {
404             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
405         }
406         return;
407     }
408 
409     if (req.ioService == nullptr)
410     {
411         messages::internalError(asyncResp->res);
412         return;
413     }
414 
415     fwAvailableTimer =
416         std::make_unique<boost::asio::steady_timer>(*req.ioService);
417 
418     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
419 
420     fwAvailableTimer->async_wait(
421         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
422 
423     task::Payload payload(req);
424     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
425         BMCWEB_LOG_DEBUG("Match fired");
426         softwareInterfaceAdded(asyncResp, m, std::move(payload));
427     };
428 
429     fwUpdateInProgress = true;
430 
431     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
432         *crow::connections::systemBus,
433         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
434         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
435         callback);
436 
437     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
438         *crow::connections::systemBus,
439         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
440         "member='InterfacesAdded',"
441         "path='/xyz/openbmc_project/logging'",
442         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
443 }
444 
445 inline std::optional<boost::urls::url>
446     parseSimpleUpdateUrl(std::string imageURI,
447                          std::optional<std::string> transferProtocol,
448                          crow::Response& res)
449 {
450     if (imageURI.find("://") == std::string::npos)
451     {
452         if (imageURI.starts_with("/"))
453         {
454             messages::actionParameterValueTypeError(
455                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
456             return std::nullopt;
457         }
458         if (!transferProtocol)
459         {
460             messages::actionParameterValueTypeError(
461                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
462             return std::nullopt;
463         }
464         // OpenBMC currently only supports TFTP
465         if (*transferProtocol == "TFTP")
466         {
467             imageURI = "tftp://" + imageURI;
468         }
469         else
470         {
471             messages::actionParameterNotSupported(res, "TransferProtocol",
472                                                   *transferProtocol);
473             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
474                              *transferProtocol);
475             return std::nullopt;
476         }
477     }
478 
479     boost::system::result<boost::urls::url> url =
480         boost::urls::parse_absolute_uri(imageURI);
481     if (!url)
482     {
483         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
484                                                 "UpdateService.SimpleUpdate");
485 
486         return std::nullopt;
487     }
488     url->normalize();
489 
490     if (url->scheme() == "tftp")
491     {
492         if (url->encoded_path().size() < 2)
493         {
494             messages::actionParameterNotSupported(res, "ImageURI",
495                                                   url->buffer());
496             return std::nullopt;
497         }
498     }
499     else
500     {
501         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
502         return std::nullopt;
503     }
504 
505     if (url->encoded_path().empty())
506     {
507         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
508                                                 "UpdateService.SimpleUpdate");
509         return std::nullopt;
510     }
511 
512     return *url;
513 }
514 
515 inline void doTftpUpdate(const crow::Request& req,
516                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
517                          const boost::urls::url_view_base& url)
518 {
519 #ifndef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
520     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
521                                           url.buffer());
522     return;
523 #endif
524 
525     std::string path(url.encoded_path());
526     if (path.size() < 2)
527     {
528         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
529                                               url.buffer());
530         return;
531     }
532     // TFTP expects a path without a /
533     path.erase(0, 1);
534     std::string host(url.encoded_host_and_port());
535     BMCWEB_LOG_DEBUG("Server: {} File: {}", host, path);
536 
537     // Setup callback for when new software detected
538     // Give TFTP 10 minutes to complete
539     monitorForSoftwareAvailable(
540         asyncResp, req,
541         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 600);
542 
543     // TFTP can take up to 10 minutes depending on image size and
544     // connection speed. Return to caller as soon as the TFTP operation
545     // has been started. The callback above will ensure the activate
546     // is started once the download has completed
547     redfish::messages::success(asyncResp->res);
548 
549     // Call TFTP service
550     crow::connections::systemBus->async_method_call(
551         [](const boost::system::error_code& ec) {
552         if (ec)
553         {
554             // messages::internalError(asyncResp->res);
555             cleanUp();
556             BMCWEB_LOG_DEBUG("error_code = {}", ec);
557             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
558         }
559         else
560         {
561             BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success");
562         }
563     },
564         "xyz.openbmc_project.Software.Download",
565         "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
566         "DownloadViaTFTP", path, host);
567 }
568 
569 inline void handleUpdateServiceSimpleUpdateAction(
570     crow::App& app, const crow::Request& req,
571     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
572 {
573     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
574     {
575         return;
576     }
577 
578     std::optional<std::string> transferProtocol;
579     std::string imageURI;
580 
581     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
582 
583     // User can pass in both TransferProtocol and ImageURI parameters or
584     // they can pass in just the ImageURI with the transfer protocol
585     // embedded within it.
586     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
587     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
588 
589     if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
590                                    transferProtocol, "ImageURI", imageURI))
591     {
592         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
593         return;
594     }
595 
596     std::optional<boost::urls::url> url =
597         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
598     if (!url)
599     {
600         return;
601     }
602     if (url->scheme() == "tftp")
603     {
604         doTftpUpdate(req, asyncResp, *url);
605     }
606     else
607     {
608         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
609                                               url->buffer());
610         return;
611     }
612 
613     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
614 }
615 
616 inline void uploadImageFile(crow::Response& res, std::string_view body)
617 {
618     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
619 
620     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
621     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
622                                     std::ofstream::trunc);
623     // set the permission of the file to 640
624     std::filesystem::perms permission = std::filesystem::perms::owner_read |
625                                         std::filesystem::perms::group_read;
626     std::filesystem::permissions(filepath, permission);
627     out << body;
628 
629     if (out.bad())
630     {
631         messages::internalError(res);
632         cleanUp();
633     }
634 }
635 
636 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
637                          const std::string& applyTime)
638 {
639     std::string applyTimeNewVal;
640     if (applyTime == "Immediate")
641     {
642         applyTimeNewVal =
643             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
644     }
645     else if (applyTime == "OnReset")
646     {
647         applyTimeNewVal =
648             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
649     }
650     else
651     {
652         BMCWEB_LOG_INFO(
653             "ApplyTime value is not in the list of acceptable values");
654         messages::propertyValueNotInList(asyncResp->res, applyTime,
655                                          "ApplyTime");
656         return;
657     }
658 
659     setDbusProperty(asyncResp, "xyz.openbmc_project.Settings",
660                     sdbusplus::message::object_path(
661                         "/xyz/openbmc_project/software/apply_time"),
662                     "xyz.openbmc_project.Software.ApplyTime",
663                     "RequestedApplyTime", "ApplyTime", applyTimeNewVal);
664 }
665 
666 inline void
667     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
668                            const MultipartParser& parser)
669 {
670     const std::string* uploadData = nullptr;
671     std::optional<std::string> applyTime = "OnReset";
672     bool targetFound = false;
673     for (const FormPart& formpart : parser.mime_fields)
674     {
675         boost::beast::http::fields::const_iterator it =
676             formpart.fields.find("Content-Disposition");
677         if (it == formpart.fields.end())
678         {
679             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
680             return;
681         }
682         BMCWEB_LOG_INFO("Parsing value {}", it->value());
683 
684         // The construction parameters of param_list must start with `;`
685         size_t index = it->value().find(';');
686         if (index == std::string::npos)
687         {
688             continue;
689         }
690 
691         for (const auto& param :
692              boost::beast::http::param_list{it->value().substr(index)})
693         {
694             if (param.first != "name" || param.second.empty())
695             {
696                 continue;
697             }
698 
699             if (param.second == "UpdateParameters")
700             {
701                 std::vector<std::string> targets;
702                 nlohmann::json content =
703                     nlohmann::json::parse(formpart.content);
704                 nlohmann::json::object_t* obj =
705                     content.get_ptr<nlohmann::json::object_t*>();
706                 if (obj == nullptr)
707                 {
708                     messages::propertyValueFormatError(asyncResp->res, targets,
709                                                        "UpdateParameters");
710                     return;
711                 }
712 
713                 if (!json_util::readJsonObject(
714                         *obj, asyncResp->res, "Targets", targets,
715                         "@Redfish.OperationApplyTime", applyTime))
716                 {
717                     return;
718                 }
719                 if (targets.size() != 1)
720                 {
721                     messages::propertyValueFormatError(asyncResp->res, targets,
722                                                        "Targets");
723                     return;
724                 }
725                 if (targets[0] != "/redfish/v1/Managers/bmc")
726                 {
727                     messages::propertyValueNotInList(asyncResp->res, targets[0],
728                                                      "Targets/0");
729                     return;
730                 }
731                 targetFound = true;
732             }
733             else if (param.second == "UpdateFile")
734             {
735                 uploadData = &(formpart.content);
736             }
737         }
738     }
739 
740     if (uploadData == nullptr)
741     {
742         BMCWEB_LOG_ERROR("Upload data is NULL");
743         messages::propertyMissing(asyncResp->res, "UpdateFile");
744         return;
745     }
746     if (!targetFound)
747     {
748         messages::propertyMissing(asyncResp->res, "targets");
749         return;
750     }
751 
752     setApplyTime(asyncResp, *applyTime);
753 
754     uploadImageFile(asyncResp->res, *uploadData);
755 }
756 
757 inline void
758     handleUpdateServicePost(App& app, const crow::Request& req,
759                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
760 {
761     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
762     {
763         return;
764     }
765     std::string_view contentType = req.getHeaderValue("Content-Type");
766 
767     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
768 
769     // Make sure that content type is application/octet-stream or
770     // multipart/form-data
771     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
772     {
773         // Setup callback for when new software detected
774         monitorForSoftwareAvailable(asyncResp, req,
775                                     "/redfish/v1/UpdateService");
776 
777         uploadImageFile(asyncResp->res, req.body());
778     }
779     else if (contentType.starts_with("multipart/form-data"))
780     {
781         MultipartParser parser;
782 
783         // Setup callback for when new software detected
784         monitorForSoftwareAvailable(asyncResp, req,
785                                     "/redfish/v1/UpdateService");
786 
787         ParserError ec = parser.parse(req);
788         if (ec != ParserError::PARSER_SUCCESS)
789         {
790             // handle error
791             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
792                              static_cast<int>(ec));
793             messages::internalError(asyncResp->res);
794             return;
795         }
796         updateMultipartContext(asyncResp, parser);
797     }
798     else
799     {
800         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
801         asyncResp->res.result(boost::beast::http::status::bad_request);
802     }
803 }
804 
805 inline void
806     handleUpdateServiceGet(App& app, const crow::Request& req,
807                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
808 {
809     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
810     {
811         return;
812     }
813     asyncResp->res.jsonValue["@odata.type"] =
814         "#UpdateService.v1_11_1.UpdateService";
815     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
816     asyncResp->res.jsonValue["Id"] = "UpdateService";
817     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
818     asyncResp->res.jsonValue["Name"] = "Update Service";
819 
820     asyncResp->res.jsonValue["HttpPushUri"] =
821         "/redfish/v1/UpdateService/update";
822     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
823         "/redfish/v1/UpdateService/update";
824 
825     // UpdateService cannot be disabled
826     asyncResp->res.jsonValue["ServiceEnabled"] = true;
827     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
828         "/redfish/v1/UpdateService/FirmwareInventory";
829     // Get the MaxImageSizeBytes
830     asyncResp->res.jsonValue["MaxImageSizeBytes"] = bmcwebHttpReqBodyLimitMb *
831                                                     1024 * 1024;
832 
833     // Update Actions object.
834     nlohmann::json& updateSvcSimpleUpdate =
835         asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
836     updateSvcSimpleUpdate["target"] =
837         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
838 
839     nlohmann::json::array_t allowed;
840 
841 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
842     allowed.emplace_back(update_service::TransferProtocolType::TFTP);
843 #endif
844 
845     updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
846         std::move(allowed);
847 
848     // Get the current ApplyTime value
849     sdbusplus::asio::getProperty<std::string>(
850         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
851         "/xyz/openbmc_project/software/apply_time",
852         "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
853         [asyncResp](const boost::system::error_code& ec,
854                     const std::string& applyTime) {
855         if (ec)
856         {
857             BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
858             messages::internalError(asyncResp->res);
859             return;
860         }
861 
862         // Store the ApplyTime Value
863         if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
864                          "RequestedApplyTimes.Immediate")
865         {
866             asyncResp->res.jsonValue["HttpPushUriOptions"]
867                                     ["HttpPushUriApplyTime"]["ApplyTime"] =
868                 "Immediate";
869         }
870         else if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
871                               "RequestedApplyTimes.OnReset")
872         {
873             asyncResp->res.jsonValue["HttpPushUriOptions"]
874                                     ["HttpPushUriApplyTime"]["ApplyTime"] =
875                 "OnReset";
876         }
877     });
878 }
879 
880 inline void handleUpdateServicePatch(
881     App& app, const crow::Request& req,
882     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
883 {
884     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
885     {
886         return;
887     }
888     BMCWEB_LOG_DEBUG("doPatch...");
889 
890     std::optional<std::string> applyTime;
891     if (!json_util::readJsonPatch(
892             req, asyncResp->res,
893             "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime))
894     {
895         return;
896     }
897 
898     if (applyTime)
899     {
900         setApplyTime(asyncResp, *applyTime);
901     }
902 }
903 
904 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
905     App& app, const crow::Request& req,
906     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
907 {
908     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
909     {
910         return;
911     }
912     asyncResp->res.jsonValue["@odata.type"] =
913         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
914     asyncResp->res.jsonValue["@odata.id"] =
915         "/redfish/v1/UpdateService/FirmwareInventory";
916     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
917     const std::array<const std::string_view, 1> iface = {
918         "xyz.openbmc_project.Software.Version"};
919 
920     redfish::collection_util::getCollectionMembers(
921         asyncResp,
922         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
923         "/xyz/openbmc_project/software");
924 }
925 
926 /* Fill related item links (i.e. bmc, bios) in for inventory */
927 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
928                             const std::string& purpose)
929 {
930     if (purpose == sw_util::bmcPurpose)
931     {
932         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
933         nlohmann::json::object_t item;
934         item["@odata.id"] = "/redfish/v1/Managers/bmc";
935         relatedItem.emplace_back(std::move(item));
936         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
937             relatedItem.size();
938     }
939     else if (purpose == sw_util::biosPurpose)
940     {
941         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
942         nlohmann::json::object_t item;
943         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
944         relatedItem.emplace_back(std::move(item));
945         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
946             relatedItem.size();
947     }
948     else
949     {
950         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
951     }
952 }
953 
954 inline void
955     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
956                        const std::string& service, const std::string& path,
957                        const std::string& swId)
958 {
959     sdbusplus::asio::getAllProperties(
960         *crow::connections::systemBus, service, path,
961         "xyz.openbmc_project.Software.Version",
962         [asyncResp,
963          swId](const boost::system::error_code& ec,
964                const dbus::utility::DBusPropertiesMap& propertiesList) {
965         if (ec)
966         {
967             messages::internalError(asyncResp->res);
968             return;
969         }
970 
971         const std::string* swInvPurpose = nullptr;
972         const std::string* version = nullptr;
973 
974         const bool success = sdbusplus::unpackPropertiesNoThrow(
975             dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
976             swInvPurpose, "Version", version);
977 
978         if (!success)
979         {
980             messages::internalError(asyncResp->res);
981             return;
982         }
983 
984         if (swInvPurpose == nullptr)
985         {
986             BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
987             messages::internalError(asyncResp->res);
988             return;
989         }
990 
991         BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
992 
993         if (version == nullptr)
994         {
995             BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
996 
997             messages::internalError(asyncResp->res);
998 
999             return;
1000         }
1001         asyncResp->res.jsonValue["Version"] = *version;
1002         asyncResp->res.jsonValue["Id"] = swId;
1003 
1004         // swInvPurpose is of format:
1005         // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1006         // Translate this to "ABC image"
1007         size_t endDesc = swInvPurpose->rfind('.');
1008         if (endDesc == std::string::npos)
1009         {
1010             messages::internalError(asyncResp->res);
1011             return;
1012         }
1013         endDesc++;
1014         if (endDesc >= swInvPurpose->size())
1015         {
1016             messages::internalError(asyncResp->res);
1017             return;
1018         }
1019 
1020         std::string formatDesc = swInvPurpose->substr(endDesc);
1021         asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1022         getRelatedItems(asyncResp, *swInvPurpose);
1023     });
1024 }
1025 
1026 inline void handleUpdateServiceFirmwareInventoryGet(
1027     App& app, const crow::Request& req,
1028     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1029     const std::string& param)
1030 {
1031     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1032     {
1033         return;
1034     }
1035     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1036 
1037     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1038         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1039 
1040     constexpr std::array<std::string_view, 1> interfaces = {
1041         "xyz.openbmc_project.Software.Version"};
1042     dbus::utility::getSubTree(
1043         "/", 0, interfaces,
1044         [asyncResp,
1045          swId](const boost::system::error_code& ec,
1046                const dbus::utility::MapperGetSubTreeResponse& subtree) {
1047         BMCWEB_LOG_DEBUG("doGet callback...");
1048         if (ec)
1049         {
1050             messages::internalError(asyncResp->res);
1051             return;
1052         }
1053 
1054         // Ensure we find our input swId, otherwise return an error
1055         bool found = false;
1056         for (const std::pair<
1057                  std::string,
1058                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1059                  obj : subtree)
1060         {
1061             if (!obj.first.ends_with(*swId))
1062             {
1063                 continue;
1064             }
1065 
1066             if (obj.second.empty())
1067             {
1068                 continue;
1069             }
1070 
1071             found = true;
1072             sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1073             getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1074                                *swId);
1075         }
1076         if (!found)
1077         {
1078             BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1079             messages::resourceMissingAtURI(
1080                 asyncResp->res,
1081                 boost::urls::format(
1082                     "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId));
1083             return;
1084         }
1085         asyncResp->res.jsonValue["@odata.type"] =
1086             "#SoftwareInventory.v1_1_0.SoftwareInventory";
1087         asyncResp->res.jsonValue["Name"] = "Software Inventory";
1088         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
1089 
1090         asyncResp->res.jsonValue["Updateable"] = false;
1091         sw_util::getSwUpdatableStatus(asyncResp, swId);
1092     });
1093 }
1094 
1095 inline void requestRoutesUpdateService(App& app)
1096 {
1097     BMCWEB_ROUTE(
1098         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1099         .privileges(redfish::privileges::postUpdateService)
1100         .methods(boost::beast::http::verb::post)(std::bind_front(
1101             handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1102 
1103     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1104         .privileges(redfish::privileges::getSoftwareInventory)
1105         .methods(boost::beast::http::verb::get)(std::bind_front(
1106             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1107 
1108     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1109         .privileges(redfish::privileges::getUpdateService)
1110         .methods(boost::beast::http::verb::get)(
1111             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1112 
1113     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1114         .privileges(redfish::privileges::patchUpdateService)
1115         .methods(boost::beast::http::verb::patch)(
1116             std::bind_front(handleUpdateServicePatch, std::ref(app)));
1117 
1118     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1119         .privileges(redfish::privileges::postUpdateService)
1120         .methods(boost::beast::http::verb::post)(
1121             std::bind_front(handleUpdateServicePost, std::ref(app)));
1122 
1123     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1124         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1125         .methods(boost::beast::http::verb::get)(std::bind_front(
1126             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1127 }
1128 
1129 } // namespace redfish
1130