1 #include "fru_parser.hpp"
2 
3 #include <nlohmann/json.hpp>
4 #include <phosphor-logging/lg2.hpp>
5 #include <xyz/openbmc_project/Common/error.hpp>
6 
7 #include <filesystem>
8 #include <fstream>
9 
10 PHOSPHOR_LOG2_USING;
11 
12 using namespace pldm::responder::dbus;
13 
14 namespace pldm
15 {
16 namespace responder
17 {
18 namespace fru_parser
19 {
20 using Json = nlohmann::json;
21 using InternalFailure =
22     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
23 
24 const Json emptyJson{};
25 const std::vector<Json> emptyJsonList{};
26 
27 FruParser::FruParser(const std::string& dirPath,
28                      const fs::path& fruMasterJsonPath)
29 {
30     if (fs::exists(fruMasterJsonPath))
31     {
32         setupDefaultDBusLookup(fruMasterJsonPath);
33     }
34     setupDefaultFruRecordMap();
35 
36     fs::path dir(dirPath);
37     if (fs::exists(dir) && !fs::is_empty(dir))
38     {
39         setupFruRecordMap(dirPath);
40     }
41 }
42 
43 void FruParser::setupDefaultDBusLookup(const fs::path& masterJsonPath)
44 {
45     constexpr auto service = "xyz.openbmc_project.Inventory.Manager";
46     constexpr auto rootPath = "/xyz/openbmc_project/inventory";
47     std::ifstream jsonFile(masterJsonPath);
48     auto data = Json::parse(jsonFile, nullptr, false);
49     if (data.is_discarded())
50     {
51         error("Failed to parse FRU Dbus Lookup Map config file '{PATH}'",
52               "PATH", masterJsonPath);
53         std::abort();
54     }
55     std::map<Interface, EntityType> defIntfToEntityType;
56     auto dbusMap = data.value("FruDBusLookupMap", emptyJson);
57     for (const auto& element : dbusMap.items())
58 
59     {
60         try
61         {
62             defIntfToEntityType[static_cast<Interface>(element.key())] =
63                 static_cast<EntityType>(element.value());
64         }
65         catch (const std::exception& e)
66         {
67             error("Failure in FRU dbus lookup map format, error - {ERROR}",
68                   "ERROR", e);
69             throw InternalFailure();
70         }
71     }
72 
73     Interfaces interfaces{};
74     for (auto [intf, entityType] : defIntfToEntityType)
75     {
76         intfToEntityType[intf] = entityType;
77         interfaces.emplace(intf);
78     }
79 
80     lookupInfo.emplace(service, rootPath, std::move(interfaces));
81 }
82 
83 void FruParser::setupDefaultFruRecordMap()
84 {
85     const FruRecordInfo generalRecordInfo = {
86         1, // generalRecordType
87         1, // encodingTypeASCII
88         {
89             // DSP0257 Table 5 General FRU Record Field Type Definitions
90             {"xyz.openbmc_project.Inventory.Decorator.Asset", "Model", "string",
91              2},
92             {"xyz.openbmc_project.Inventory.Decorator.Asset", "PartNumber",
93              "string", 3},
94             {"xyz.openbmc_project.Inventory.Decorator.Asset", "SerialNumber",
95              "string", 4},
96             {"xyz.openbmc_project.Inventory.Decorator.Asset", "Manufacturer",
97              "string", 5},
98             {"xyz.openbmc_project.Inventory.Item", "PrettyName", "string", 8},
99             {"xyz.openbmc_project.Inventory.Decorator.AssetTag", "AssetTag",
100              "string", 11},
101             {"xyz.openbmc_project.Inventory.Decorator.Revision", "Version",
102              "string", 10},
103         }};
104 
105     for (auto [intf, entityType] : intfToEntityType)
106     {
107         recordMap[intf] = {generalRecordInfo};
108     }
109 }
110 
111 void FruParser::setupFruRecordMap(const std::string& dirPath)
112 {
113     for (auto& file : fs::directory_iterator(dirPath))
114     {
115         auto fileName = file.path().filename().string();
116         std::ifstream jsonFile(file.path());
117         auto data = Json::parse(jsonFile, nullptr, false);
118         if (data.is_discarded())
119         {
120             error("Failed to parse FRU config file at '{PATH}'", "PATH",
121                   file.path());
122             throw InternalFailure();
123         }
124 
125         try
126         {
127             auto record = data.value("record_details", emptyJson);
128             auto recordType =
129                 static_cast<uint8_t>(record.value("fru_record_type", 0));
130             auto encType =
131                 static_cast<uint8_t>(record.value("fru_encoding_type", 0));
132             auto dbusIntfName = record.value("dbus_interface_name", "");
133             auto entries = data.value("fru_fields", emptyJsonList);
134             std::vector<FieldInfo> fieldInfo;
135 
136             for (const auto& entry : entries)
137             {
138                 auto fieldType =
139                     static_cast<uint8_t>(entry.value("fru_field_type", 0));
140                 auto dbus = entry.value("dbus", emptyJson);
141                 auto interface = dbus.value("interface", "");
142                 auto property = dbus.value("property_name", "");
143                 auto propType = dbus.value("property_type", "");
144                 fieldInfo.emplace_back(
145                     std::make_tuple(std::move(interface), std::move(property),
146                                     std::move(propType), std::move(fieldType)));
147             }
148 
149             FruRecordInfo fruInfo;
150             fruInfo = std::make_tuple(recordType, encType,
151                                       std::move(fieldInfo));
152 
153             auto search = recordMap.find(dbusIntfName);
154 
155             // PLDM FRU can have multiple records for the same FRU like General
156             // FRU record and multiple OEM FRU records. If the FRU item
157             // interface name is already in the map, that indicates a record
158             // info is already added for the FRU, so append the new record info
159             // to the same data.
160             if (search != recordMap.end())
161             {
162                 search->second.emplace_back(std::move(fruInfo));
163             }
164             else
165             {
166                 FruRecordInfos recordInfos{fruInfo};
167                 recordMap.emplace(dbusIntfName, recordInfos);
168             }
169         }
170         catch (const std::exception&)
171         {
172             continue;
173         }
174     }
175 }
176 
177 } // namespace fru_parser
178 
179 } // namespace responder
180 
181 } // namespace pldm
182