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