1 #include "fru_parser.hpp"
2 
3 #include <nlohmann/json.hpp>
4 #include <xyz/openbmc_project/Common/error.hpp>
5 
6 #include <filesystem>
7 #include <fstream>
8 #include <iostream>
9 
10 namespace pldm
11 {
12 
13 namespace responder
14 {
15 
16 namespace fru_parser
17 {
18 
19 using Json = nlohmann::json;
20 using InternalFailure =
21     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
22 
23 const Json emptyJson{};
24 const std::vector<Json> emptyJsonList{};
25 const std::vector<std::string> emptyStringVec{};
26 
27 constexpr auto fruMasterJson = "FRU_Master.json";
28 
29 FruParser::FruParser(const std::string& dirPath)
30 {
31     fs::path dir(dirPath);
32     if (!fs::exists(dir) || fs::is_empty(dir))
33     {
34         setupDefaultDBusLookup();
35         setupDefaultFruRecordMap();
36         return;
37     }
38 
39     fs::path masterFilePath = dir / fruMasterJson;
40     if (!fs::exists(masterFilePath))
41     {
42         std::cerr << "FRU D-Bus lookup JSON does not exist, PATH="
43                   << masterFilePath << "\n";
44         throw InternalFailure();
45     }
46 
47     setupDBusLookup(masterFilePath);
48     setupFruRecordMap(dirPath);
49 }
50 
51 void FruParser::setupDefaultDBusLookup()
52 {
53     constexpr auto service = "xyz.openbmc_project.Inventory.Manager";
54     constexpr auto rootPath = "/xyz/openbmc_project/inventory";
55 
56     // DSP0249 1.0.0    Table 15 Entity ID Codes
57     const std::map<Interface, EntityType> defIntfToEntityType = {
58         {"xyz.openbmc_project.Inventory.Item.Chassis", 45},
59         {"xyz.openbmc_project.Inventory.Item.Board", 60},
60         {"xyz.openbmc_project.Inventory.Item.Board.Motherboard", 64},
61         {"xyz.openbmc_project.Inventory.Item.Panel", 69},
62         {"xyz.openbmc_project.Inventory.Item.PowerSupply", 120},
63         {"xyz.openbmc_project.Inventory.Item.Vrm", 123},
64         {"xyz.openbmc_project.Inventory.Item.Cpu", 135},
65         {"xyz.openbmc_project.Inventory.Item.Bmc", 137},
66         {"xyz.openbmc_project.Inventory.Item.Dimm", 142},
67     };
68 
69     Interfaces interfaces{};
70     for (auto [intf, entityType] : defIntfToEntityType)
71     {
72         intfToEntityType[intf] = entityType;
73         interfaces.emplace(intf);
74     }
75 
76     lookupInfo.emplace(service, rootPath, std::move(interfaces));
77 }
78 
79 void FruParser::setupDBusLookup(const fs::path& filePath)
80 {
81     std::ifstream jsonFile(filePath);
82 
83     auto data = Json::parse(jsonFile, nullptr, false);
84     if (data.is_discarded())
85     {
86         std::cerr << "Parsing FRU master config file failed, FILE=" << filePath;
87         throw InternalFailure();
88     }
89 
90     Service service = data.value("service", "");
91     RootPath rootPath = data.value("root_path", "");
92     auto entities = data.value("entities", emptyJsonList);
93     Interfaces interfaces{};
94     EntityType entityType{};
95     for (auto& entity : entities)
96     {
97         auto intf = entity.value("interface", "");
98         intfToEntityType[intf] =
99             std::move(entity.value("entity_type", entityType));
100         interfaces.emplace(std::move(intf));
101     }
102     lookupInfo.emplace(std::make_tuple(std::move(service), std::move(rootPath),
103                                        std::move(interfaces)));
104 }
105 
106 void FruParser::setupDefaultFruRecordMap()
107 {
108     const FruRecordInfo generalRecordInfo = {
109         1, // generalRecordType
110         1, // encodingTypeASCII
111         {
112             // DSP0257 Table 5 General FRU Record Field Type Definitions
113             {"xyz.openbmc_project.Inventory.Decorator.Asset", "Model", "string",
114              2},
115             {"xyz.openbmc_project.Inventory.Decorator.Asset", "PartNumber",
116              "string", 3},
117             {"xyz.openbmc_project.Inventory.Decorator.Asset", "SerialNumber",
118              "string", 4},
119             {"xyz.openbmc_project.Inventory.Decorator.Asset", "Manufacturer",
120              "string", 5},
121             {"xyz.openbmc_project.Inventory.Item", "PrettyName", "string", 8},
122             {"xyz.openbmc_project.Inventory.Decorator.AssetTag", "AssetTag",
123              "string", 11},
124             {"xyz.openbmc_project.Inventory.Decorator.Revision", "Version",
125              "string", 10},
126         }};
127 
128     for (auto [intf, entityType] : intfToEntityType)
129     {
130         recordMap[intf] = {generalRecordInfo};
131     }
132 }
133 
134 void FruParser::setupFruRecordMap(const std::string& dirPath)
135 {
136     for (auto& file : fs::directory_iterator(dirPath))
137     {
138         auto fileName = file.path().filename().string();
139         if (fruMasterJson == fileName)
140         {
141             continue;
142         }
143 
144         std::ifstream jsonFile(file.path());
145         auto data = Json::parse(jsonFile, nullptr, false);
146         if (data.is_discarded())
147         {
148 
149             std::cerr << "Parsing FRU master config file failed, FILE="
150                       << file.path();
151             throw InternalFailure();
152         }
153 
154         try
155         {
156             auto record = data.value("record_details", emptyJson);
157             auto recordType =
158                 static_cast<uint8_t>(record.value("fru_record_type", 0));
159             auto encType =
160                 static_cast<uint8_t>(record.value("fru_encoding_type", 0));
161             auto dbusIntfName = record.value("dbus_interface_name", "");
162             auto entries = data.value("fru_fields", emptyJsonList);
163             std::vector<FieldInfo> fieldInfo;
164 
165             for (const auto& entry : entries)
166             {
167                 auto fieldType =
168                     static_cast<uint8_t>(entry.value("fru_field_type", 0));
169                 auto dbus = entry.value("dbus", emptyJson);
170                 auto interface = dbus.value("interface", "");
171                 auto property = dbus.value("property_name", "");
172                 auto propType = dbus.value("property_type", "");
173                 fieldInfo.emplace_back(
174                     std::make_tuple(std::move(interface), std::move(property),
175                                     std::move(propType), std::move(fieldType)));
176             }
177 
178             FruRecordInfo fruInfo;
179             fruInfo =
180                 std::make_tuple(recordType, encType, std::move(fieldInfo));
181 
182             auto search = recordMap.find(dbusIntfName);
183 
184             // PLDM FRU can have multiple records for the same FRU like General
185             // FRU record and multiple OEM FRU records. If the FRU item
186             // interface name is already in the map, that indicates a record
187             // info is already added for the FRU, so append the new record info
188             // to the same data.
189             if (search != recordMap.end())
190             {
191                 search->second.emplace_back(std::move(fruInfo));
192             }
193             else
194             {
195                 FruRecordInfos recordInfos{fruInfo};
196                 recordMap.emplace(dbusIntfName, recordInfos);
197             }
198         }
199         catch (const std::exception& e)
200         {
201             continue;
202         }
203     }
204 }
205 
206 } // namespace fru_parser
207 
208 } // namespace responder
209 
210 } // namespace pldm
211