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