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