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 "node.hpp"
19 
20 #include <boost/container/flat_map.hpp>
21 #include <utils/fw_utils.hpp>
22 #include <variant>
23 
24 namespace redfish
25 {
26 
27 // Match signals added on software path
28 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher;
29 // Only allow one update at a time
30 static bool fwUpdateInProgress = false;
31 // Timer for software available
32 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
33 
34 static void cleanUp()
35 {
36     fwUpdateInProgress = false;
37     fwUpdateMatcher = nullptr;
38 }
39 static void activateImage(const std::string &objPath,
40                           const std::string &service)
41 {
42     BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
43     crow::connections::systemBus->async_method_call(
44         [](const boost::system::error_code error_code) {
45             if (error_code)
46             {
47                 BMCWEB_LOG_DEBUG << "error_code = " << error_code;
48                 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message();
49             }
50         },
51         service, objPath, "org.freedesktop.DBus.Properties", "Set",
52         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
53         std::variant<std::string>(
54             "xyz.openbmc_project.Software.Activation.RequestedActivations."
55             "Active"));
56 }
57 
58 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
59 // then no asyncResp updates will occur
60 static void softwareInterfaceAdded(std::shared_ptr<AsyncResp> asyncResp,
61                                    sdbusplus::message::message &m)
62 {
63     std::vector<std::pair<
64         std::string,
65         std::vector<std::pair<std::string, std::variant<std::string>>>>>
66         interfacesProperties;
67 
68     sdbusplus::message::object_path objPath;
69 
70     m.read(objPath, interfacesProperties);
71 
72     BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
73     for (auto &interface : interfacesProperties)
74     {
75         BMCWEB_LOG_DEBUG << "interface = " << interface.first;
76 
77         if (interface.first == "xyz.openbmc_project.Software.Activation")
78         {
79             // Found our interface, disable callbacks
80             fwUpdateMatcher = nullptr;
81 
82             // Retrieve service and activate
83             crow::connections::systemBus->async_method_call(
84                 [objPath, asyncResp](
85                     const boost::system::error_code error_code,
86                     const std::vector<std::pair<
87                         std::string, std::vector<std::string>>> &objInfo) {
88                     if (error_code)
89                     {
90                         BMCWEB_LOG_DEBUG << "error_code = " << error_code;
91                         BMCWEB_LOG_DEBUG << "error msg = "
92                                          << error_code.message();
93                         if (asyncResp)
94                         {
95                             messages::internalError(asyncResp->res);
96                         }
97                         cleanUp();
98                         return;
99                     }
100                     // Ensure we only got one service back
101                     if (objInfo.size() != 1)
102                     {
103                         BMCWEB_LOG_ERROR << "Invalid Object Size "
104                                          << objInfo.size();
105                         if (asyncResp)
106                         {
107                             messages::internalError(asyncResp->res);
108                         }
109                         cleanUp();
110                         return;
111                     }
112                     // cancel timer only when
113                     // xyz.openbmc_project.Software.Activation interface
114                     // is added
115                     fwAvailableTimer = nullptr;
116 
117                     activateImage(objPath.str, objInfo[0].first);
118                     if (asyncResp)
119                     {
120                         redfish::messages::success(asyncResp->res);
121                     }
122                     fwUpdateInProgress = false;
123                 },
124                 "xyz.openbmc_project.ObjectMapper",
125                 "/xyz/openbmc_project/object_mapper",
126                 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str,
127                 std::array<const char *, 1>{
128                     "xyz.openbmc_project.Software.Activation"});
129         }
130     }
131 }
132 
133 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
134 // then no asyncResp updates will occur
135 static void monitorForSoftwareAvailable(std::shared_ptr<AsyncResp> asyncResp,
136                                         const crow::Request &req,
137                                         int timeoutTimeSeconds = 5)
138 {
139     // Only allow one FW update at a time
140     if (fwUpdateInProgress != false)
141     {
142         if (asyncResp)
143         {
144             asyncResp->res.addHeader("Retry-After", "30");
145             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
146         }
147         return;
148     }
149 
150     fwAvailableTimer =
151         std::make_unique<boost::asio::steady_timer>(*req.ioService);
152 
153     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
154 
155     fwAvailableTimer->async_wait(
156         [asyncResp](const boost::system::error_code &ec) {
157             cleanUp();
158             if (ec == boost::asio::error::operation_aborted)
159             {
160                 // expected, we were canceled before the timer completed.
161                 return;
162             }
163             BMCWEB_LOG_ERROR
164                 << "Timed out waiting for firmware object being created";
165             BMCWEB_LOG_ERROR
166                 << "FW image may has already been uploaded to server";
167             if (ec)
168             {
169                 BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
170                 return;
171             }
172             if (asyncResp)
173             {
174                 redfish::messages::internalError(asyncResp->res);
175             }
176         });
177 
178     auto callback = [asyncResp](sdbusplus::message::message &m) {
179         BMCWEB_LOG_DEBUG << "Match fired";
180         softwareInterfaceAdded(asyncResp, m);
181     };
182 
183     fwUpdateInProgress = true;
184 
185     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>(
186         *crow::connections::systemBus,
187         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
188         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
189         callback);
190 }
191 
192 /**
193  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
194  * SimpleUpdate action.
195  */
196 class UpdateServiceActionsSimpleUpdate : public Node
197 {
198   public:
199     UpdateServiceActionsSimpleUpdate(CrowApp &app) :
200         Node(app,
201              "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
202     {
203         entityPrivileges = {
204             {boost::beast::http::verb::get, {{"Login"}}},
205             {boost::beast::http::verb::head, {{"Login"}}},
206             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
207             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
208             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
209             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
210     }
211 
212   private:
213     void doPost(crow::Response &res, const crow::Request &req,
214                 const std::vector<std::string> &params) override
215     {
216         std::optional<std::string> transferProtocol;
217         std::string imageURI;
218         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
219 
220         BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
221 
222         // User can pass in both TransferProtocol and ImageURI parameters or
223         // they can pass in just the ImageURI with the transfer protocl embedded
224         // within it.
225         // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
226         // 2) ImageURI:tftp://1.1.1.1/myfile.bin
227 
228         if (!json_util::readJson(req, asyncResp->res, "TransferProtocol",
229                                  transferProtocol, "ImageURI", imageURI))
230         {
231             BMCWEB_LOG_DEBUG
232                 << "Missing TransferProtocol or ImageURI parameter";
233             return;
234         }
235         if (!transferProtocol)
236         {
237             // Must be option 2
238             // Verify ImageURI has transfer protocol in it
239             size_t separator = imageURI.find(":");
240             if ((separator == std::string::npos) ||
241                 ((separator + 1) > imageURI.size()))
242             {
243                 messages::actionParameterValueTypeError(
244                     asyncResp->res, imageURI, "ImageURI",
245                     "UpdateService.SimpleUpdate");
246                 BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: "
247                                  << imageURI;
248                 return;
249             }
250             transferProtocol = imageURI.substr(0, separator);
251             // Ensure protocol is upper case for a common comparison path below
252             boost::to_upper(*transferProtocol);
253             BMCWEB_LOG_DEBUG << "Encoded transfer protocol "
254                              << *transferProtocol;
255 
256             // Adjust imageURI to not have the protocol on it for parsing
257             // below
258             // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
259             imageURI = imageURI.substr(separator + 3);
260             BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
261         }
262 
263         // OpenBMC currently only supports TFTP
264         if (*transferProtocol != "TFTP")
265         {
266             messages::actionParameterNotSupported(asyncResp->res,
267                                                   "TransferProtocol",
268                                                   "UpdateService.SimpleUpdate");
269             BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
270                              << *transferProtocol;
271             return;
272         }
273 
274         // Format should be <IP or Hostname>/<file> for imageURI
275         size_t separator = imageURI.find("/");
276         if ((separator == std::string::npos) ||
277             ((separator + 1) > imageURI.size()))
278         {
279             messages::actionParameterValueTypeError(
280                 asyncResp->res, imageURI, "ImageURI",
281                 "UpdateService.SimpleUpdate");
282             BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
283             return;
284         }
285 
286         std::string tftpServer = imageURI.substr(0, separator);
287         std::string fwFile = imageURI.substr(separator + 1);
288         BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
289 
290         // Setup callback for when new software detected
291         // Give TFTP 2 minutes to complete
292         monitorForSoftwareAvailable(nullptr, req, 120);
293 
294         // TFTP can take up to 2 minutes depending on image size and
295         // connection speed. Return to caller as soon as the TFTP operation
296         // has been started. The callback above will ensure the activate
297         // is started once the download has completed
298         redfish::messages::success(asyncResp->res);
299 
300         // Call TFTP service
301         crow::connections::systemBus->async_method_call(
302             [](const boost::system::error_code ec) {
303                 if (ec)
304                 {
305                     // messages::internalError(asyncResp->res);
306                     cleanUp();
307                     BMCWEB_LOG_DEBUG << "error_code = " << ec;
308                     BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
309                 }
310                 else
311                 {
312                     BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
313                 }
314             },
315             "xyz.openbmc_project.Software.Download",
316             "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
317             "DownloadViaTFTP", fwFile, tftpServer);
318 
319         BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
320     }
321 };
322 
323 class UpdateService : public Node
324 {
325   public:
326     UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/")
327     {
328         entityPrivileges = {
329             {boost::beast::http::verb::get, {{"Login"}}},
330             {boost::beast::http::verb::head, {{"Login"}}},
331             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
332             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
333             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
334             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
335     }
336 
337   private:
338     void doGet(crow::Response &res, const crow::Request &req,
339                const std::vector<std::string> &params) override
340     {
341         res.jsonValue["@odata.type"] = "#UpdateService.v1_2_0.UpdateService";
342         res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
343         res.jsonValue["@odata.context"] =
344             "/redfish/v1/$metadata#UpdateService.UpdateService";
345         res.jsonValue["Id"] = "UpdateService";
346         res.jsonValue["Description"] = "Service for Software Update";
347         res.jsonValue["Name"] = "Update Service";
348         res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService";
349         // UpdateService cannot be disabled
350         res.jsonValue["ServiceEnabled"] = true;
351         res.jsonValue["FirmwareInventory"] = {
352             {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}};
353 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
354         // Update Actions object.
355         nlohmann::json &updateSvcSimpleUpdate =
356             res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
357         updateSvcSimpleUpdate["target"] =
358             "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
359         updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {
360             "TFTP"};
361 #endif
362         res.end();
363     }
364 
365     void doPatch(crow::Response &res, const crow::Request &req,
366                  const std::vector<std::string> &params) override
367     {
368         BMCWEB_LOG_DEBUG << "doPatch...";
369 
370         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
371         std::string applyTime;
372 
373         if (!json_util::readJson(req, res, "ApplyTime", applyTime))
374         {
375             return;
376         }
377 
378         if ((applyTime == "Immediate") || (applyTime == "OnReset"))
379         {
380             std::string applyTimeNewVal;
381             if (applyTime == "Immediate")
382             {
383                 applyTimeNewVal = "xyz.openbmc_project.Software.ApplyTime."
384                                   "RequestedApplyTimes.Immediate";
385             }
386             else
387             {
388                 applyTimeNewVal = "xyz.openbmc_project.Software.ApplyTime."
389                                   "RequestedApplyTimes.OnReset";
390             }
391 
392             // Set the requested image apply time value
393             crow::connections::systemBus->async_method_call(
394                 [asyncResp](const boost::system::error_code ec) {
395                     if (ec)
396                     {
397                         BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
398                         messages::internalError(asyncResp->res);
399                         return;
400                     }
401                     messages::success(asyncResp->res);
402                 },
403                 "xyz.openbmc_project.Settings",
404                 "/xyz/openbmc_project/software/apply_time",
405                 "org.freedesktop.DBus.Properties", "Set",
406                 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
407                 std::variant<std::string>{applyTimeNewVal});
408         }
409         else
410         {
411             BMCWEB_LOG_INFO << "ApplyTime value is not in the list of "
412                                "acceptable values";
413             messages::propertyValueNotInList(asyncResp->res, applyTime,
414                                              "ApplyTime");
415         }
416     }
417 
418     void doPost(crow::Response &res, const crow::Request &req,
419                 const std::vector<std::string> &params) override
420     {
421         BMCWEB_LOG_DEBUG << "doPost...";
422 
423         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
424 
425         // Setup callback for when new software detected
426         monitorForSoftwareAvailable(asyncResp, req);
427 
428         std::string filepath(
429             "/tmp/images/" +
430             boost::uuids::to_string(boost::uuids::random_generator()()));
431         BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
432         std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
433                                         std::ofstream::trunc);
434         out << req.body;
435         out.close();
436         BMCWEB_LOG_DEBUG << "file upload complete!!";
437     }
438 };
439 
440 class SoftwareInventoryCollection : public Node
441 {
442   public:
443     template <typename CrowApp>
444     SoftwareInventoryCollection(CrowApp &app) :
445         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/")
446     {
447         entityPrivileges = {
448             {boost::beast::http::verb::get, {{"Login"}}},
449             {boost::beast::http::verb::head, {{"Login"}}},
450             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
451             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
452             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
453             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
454     }
455 
456   private:
457     void doGet(crow::Response &res, const crow::Request &req,
458                const std::vector<std::string> &params) override
459     {
460         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
461         res.jsonValue["@odata.type"] =
462             "#SoftwareInventoryCollection.SoftwareInventoryCollection";
463         res.jsonValue["@odata.id"] =
464             "/redfish/v1/UpdateService/FirmwareInventory";
465         res.jsonValue["@odata.context"] =
466             "/redfish/v1/"
467             "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection";
468         res.jsonValue["Name"] = "Software Inventory Collection";
469 
470         crow::connections::systemBus->async_method_call(
471             [asyncResp](
472                 const boost::system::error_code ec,
473                 const std::vector<std::pair<
474                     std::string, std::vector<std::pair<
475                                      std::string, std::vector<std::string>>>>>
476                     &subtree) {
477                 if (ec)
478                 {
479                     messages::internalError(asyncResp->res);
480                     return;
481                 }
482                 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
483                 asyncResp->res.jsonValue["Members@odata.count"] = 0;
484 
485                 for (auto &obj : subtree)
486                 {
487                     // if can't parse fw id then return
488                     std::size_t idPos;
489                     if ((idPos = obj.first.rfind("/")) == std::string::npos)
490                     {
491                         messages::internalError(asyncResp->res);
492                         BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
493                         return;
494                     }
495                     std::string swId = obj.first.substr(idPos + 1);
496 
497                     nlohmann::json &members =
498                         asyncResp->res.jsonValue["Members"];
499                     members.push_back(
500                         {{"@odata.id", "/redfish/v1/UpdateService/"
501                                        "FirmwareInventory/" +
502                                            swId}});
503                     asyncResp->res.jsonValue["Members@odata.count"] =
504                         members.size();
505                 }
506             },
507             "xyz.openbmc_project.ObjectMapper",
508             "/xyz/openbmc_project/object_mapper",
509             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
510             static_cast<int32_t>(0),
511             std::array<const char *, 1>{
512                 "xyz.openbmc_project.Software.Version"});
513     }
514 };
515 
516 class SoftwareInventory : public Node
517 {
518   public:
519     template <typename CrowApp>
520     SoftwareInventory(CrowApp &app) :
521         Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/",
522              std::string())
523     {
524         entityPrivileges = {
525             {boost::beast::http::verb::get, {{"Login"}}},
526             {boost::beast::http::verb::head, {{"Login"}}},
527             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
528             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
529             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
530             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
531     }
532 
533   private:
534     /* Fill related item links (i.e. bmc, bios) in for inventory */
535     static void getRelatedItems(std::shared_ptr<AsyncResp> aResp,
536                                 const std::string &purpose)
537     {
538         if (purpose == fw_util::bmcPurpose)
539         {
540             nlohmann::json &members = aResp->res.jsonValue["RelatedItem"];
541             members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}});
542             aResp->res.jsonValue["Members@odata.count"] = members.size();
543         }
544         else if (purpose == fw_util::biosPurpose)
545         {
546             // TODO(geissonator) Need BIOS schema support added for this
547             //                   to be valid
548             // nlohmann::json &members = aResp->res.jsonValue["RelatedItem"];
549             // members.push_back(
550             //    {{"@odata.id", "/redfish/v1/Systems/system/BIOS"}});
551             // aResp->res.jsonValue["Members@odata.count"] = members.size();
552         }
553         else
554         {
555             BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
556         }
557     }
558 
559     void doGet(crow::Response &res, const crow::Request &req,
560                const std::vector<std::string> &params) override
561     {
562         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
563 
564         if (params.size() != 1)
565         {
566             messages::internalError(res);
567             res.end();
568             return;
569         }
570 
571         std::shared_ptr<std::string> swId =
572             std::make_shared<std::string>(params[0]);
573 
574         res.jsonValue["@odata.id"] =
575             "/redfish/v1/UpdateService/FirmwareInventory/" + *swId;
576 
577         crow::connections::systemBus->async_method_call(
578             [asyncResp, swId](
579                 const boost::system::error_code ec,
580                 const std::vector<std::pair<
581                     std::string, std::vector<std::pair<
582                                      std::string, std::vector<std::string>>>>>
583                     &subtree) {
584                 BMCWEB_LOG_DEBUG << "doGet callback...";
585                 if (ec)
586                 {
587                     messages::internalError(asyncResp->res);
588                     return;
589                 }
590 
591                 // Ensure we find our input swId, otherwise return an error
592                 bool found = false;
593                 for (const std::pair<
594                          std::string,
595                          std::vector<
596                              std::pair<std::string, std::vector<std::string>>>>
597                          &obj : subtree)
598                 {
599                     if (boost::ends_with(obj.first, *swId) != true)
600                     {
601                         continue;
602                     }
603 
604                     if (obj.second.size() < 1)
605                     {
606                         continue;
607                     }
608 
609                     found = true;
610                     fw_util::getFwStatus(asyncResp, swId, obj.second[0].first);
611 
612                     crow::connections::systemBus->async_method_call(
613                         [asyncResp,
614                          swId](const boost::system::error_code error_code,
615                                const boost::container::flat_map<
616                                    std::string, VariantType> &propertiesList) {
617                             if (error_code)
618                             {
619                                 messages::internalError(asyncResp->res);
620                                 return;
621                             }
622                             boost::container::flat_map<
623                                 std::string, VariantType>::const_iterator it =
624                                 propertiesList.find("Purpose");
625                             if (it == propertiesList.end())
626                             {
627                                 BMCWEB_LOG_DEBUG
628                                     << "Can't find property \"Purpose\"!";
629                                 messages::propertyMissing(asyncResp->res,
630                                                           "Purpose");
631                                 return;
632                             }
633                             const std::string *swInvPurpose =
634                                 std::get_if<std::string>(&it->second);
635                             if (swInvPurpose == nullptr)
636                             {
637                                 BMCWEB_LOG_DEBUG
638                                     << "wrong types for property\"Purpose\"!";
639                                 messages::propertyValueTypeError(asyncResp->res,
640                                                                  "", "Purpose");
641                                 return;
642                             }
643 
644                             BMCWEB_LOG_DEBUG << "swInvPurpose = "
645                                              << *swInvPurpose;
646                             it = propertiesList.find("Version");
647                             if (it == propertiesList.end())
648                             {
649                                 BMCWEB_LOG_DEBUG
650                                     << "Can't find property \"Version\"!";
651                                 messages::propertyMissing(asyncResp->res,
652                                                           "Version");
653                                 return;
654                             }
655 
656                             BMCWEB_LOG_DEBUG << "Version found!";
657 
658                             const std::string *version =
659                                 std::get_if<std::string>(&it->second);
660 
661                             if (version == nullptr)
662                             {
663                                 BMCWEB_LOG_DEBUG
664                                     << "Can't find property \"Version\"!";
665 
666                                 messages::propertyValueTypeError(asyncResp->res,
667                                                                  "", "Version");
668                                 return;
669                             }
670                             asyncResp->res.jsonValue["Version"] = *version;
671                             asyncResp->res.jsonValue["Id"] = *swId;
672 
673                             // swInvPurpose is of format:
674                             // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
675                             // Translate this to "ABC image"
676                             size_t endDesc = swInvPurpose->rfind(".");
677                             if (endDesc == std::string::npos)
678                             {
679                                 messages::internalError(asyncResp->res);
680                                 return;
681                             }
682                             endDesc++;
683                             if (endDesc >= swInvPurpose->size())
684                             {
685                                 messages::internalError(asyncResp->res);
686                                 return;
687                             }
688 
689                             std::string formatDesc =
690                                 swInvPurpose->substr(endDesc);
691                             asyncResp->res.jsonValue["Description"] =
692                                 formatDesc + " image";
693                             getRelatedItems(asyncResp, *swInvPurpose);
694                         },
695                         obj.second[0].first, obj.first,
696                         "org.freedesktop.DBus.Properties", "GetAll",
697                         "xyz.openbmc_project.Software.Version");
698                 }
699                 if (!found)
700                 {
701                     BMCWEB_LOG_ERROR << "Input swID " + *swId + " not found!";
702                     messages::resourceMissingAtURI(
703                         asyncResp->res,
704                         "/redfish/v1/UpdateService/FirmwareInventory/" + *swId);
705                     return;
706                 }
707                 asyncResp->res.jsonValue["@odata.type"] =
708                     "#SoftwareInventory.v1_1_0.SoftwareInventory";
709                 asyncResp->res.jsonValue["@odata.context"] =
710                     "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory";
711                 asyncResp->res.jsonValue["Name"] = "Software Inventory";
712                 asyncResp->res.jsonValue["Updateable"] = false;
713                 asyncResp->res.jsonValue["Status"]["Health"] = "OK";
714                 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
715             },
716             "xyz.openbmc_project.ObjectMapper",
717             "/xyz/openbmc_project/object_mapper",
718             "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/",
719             static_cast<int32_t>(0),
720             std::array<const char *, 1>{
721                 "xyz.openbmc_project.Software.Version"});
722     }
723 };
724 
725 } // namespace redfish
726