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 FruParser::FruParser(const std::string& dirPath)
28 {
29     setupDefaultDBusLookup();
30     setupDefaultFruRecordMap();
31 
32     fs::path dir(dirPath);
33     if (fs::exists(dir) && !fs::is_empty(dir))
34     {
35         setupFruRecordMap(dirPath);
36     }
37 }
38 
39 void FruParser::setupDefaultDBusLookup()
40 {
41     constexpr auto service = "xyz.openbmc_project.Inventory.Manager";
42     constexpr auto rootPath = "/xyz/openbmc_project/inventory";
43 
44     // DSP0249 1.0.0    Table 15 Entity ID Codes
45     const std::map<Interface, EntityType> defIntfToEntityType = {
46         {"xyz.openbmc_project.Inventory.Item.Chassis", 45},
47         {"xyz.openbmc_project.Inventory.Item.Board", 60},
48         {"xyz.openbmc_project.Inventory.Item.Board.Motherboard", 64},
49         {"xyz.openbmc_project.Inventory.Item.Panel", 69},
50         {"xyz.openbmc_project.Inventory.Item.PowerSupply", 120},
51         {"xyz.openbmc_project.Inventory.Item.Vrm", 123},
52         {"xyz.openbmc_project.Inventory.Item.Cpu", 135},
53         {"xyz.openbmc_project.Inventory.Item.Bmc", 137},
54         {"xyz.openbmc_project.Inventory.Item.Dimm", 142},
55         {"xyz.openbmc_project.Inventory.Item.Tpm", 24576},
56         {"xyz.openbmc_project.Inventory.Item.System", 11521},
57     };
58 
59     Interfaces interfaces{};
60     for (auto [intf, entityType] : defIntfToEntityType)
61     {
62         intfToEntityType[intf] = entityType;
63         interfaces.emplace(intf);
64     }
65 
66     lookupInfo.emplace(service, rootPath, std::move(interfaces));
67 }
68 
69 void FruParser::setupDefaultFruRecordMap()
70 {
71     const FruRecordInfo generalRecordInfo = {
72         1, // generalRecordType
73         1, // encodingTypeASCII
74         {
75             // DSP0257 Table 5 General FRU Record Field Type Definitions
76             {"xyz.openbmc_project.Inventory.Decorator.Asset", "Model", "string",
77              2},
78             {"xyz.openbmc_project.Inventory.Decorator.Asset", "PartNumber",
79              "string", 3},
80             {"xyz.openbmc_project.Inventory.Decorator.Asset", "SerialNumber",
81              "string", 4},
82             {"xyz.openbmc_project.Inventory.Decorator.Asset", "Manufacturer",
83              "string", 5},
84             {"xyz.openbmc_project.Inventory.Item", "PrettyName", "string", 8},
85             {"xyz.openbmc_project.Inventory.Decorator.AssetTag", "AssetTag",
86              "string", 11},
87             {"xyz.openbmc_project.Inventory.Decorator.Revision", "Version",
88              "string", 10},
89         }};
90 
91     for (auto [intf, entityType] : intfToEntityType)
92     {
93         recordMap[intf] = {generalRecordInfo};
94     }
95 }
96 
97 void FruParser::setupFruRecordMap(const std::string& dirPath)
98 {
99     for (auto& file : fs::directory_iterator(dirPath))
100     {
101         auto fileName = file.path().filename().string();
102         std::ifstream jsonFile(file.path());
103         auto data = Json::parse(jsonFile, nullptr, false);
104         if (data.is_discarded())
105         {
106 
107             std::cerr << "Parsing FRU config file failed, FILE=" << file.path();
108             throw InternalFailure();
109         }
110 
111         try
112         {
113             auto record = data.value("record_details", emptyJson);
114             auto recordType =
115                 static_cast<uint8_t>(record.value("fru_record_type", 0));
116             auto encType =
117                 static_cast<uint8_t>(record.value("fru_encoding_type", 0));
118             auto dbusIntfName = record.value("dbus_interface_name", "");
119             auto entries = data.value("fru_fields", emptyJsonList);
120             std::vector<FieldInfo> fieldInfo;
121 
122             for (const auto& entry : entries)
123             {
124                 auto fieldType =
125                     static_cast<uint8_t>(entry.value("fru_field_type", 0));
126                 auto dbus = entry.value("dbus", emptyJson);
127                 auto interface = dbus.value("interface", "");
128                 auto property = dbus.value("property_name", "");
129                 auto propType = dbus.value("property_type", "");
130                 fieldInfo.emplace_back(
131                     std::make_tuple(std::move(interface), std::move(property),
132                                     std::move(propType), std::move(fieldType)));
133             }
134 
135             FruRecordInfo fruInfo;
136             fruInfo =
137                 std::make_tuple(recordType, encType, std::move(fieldInfo));
138 
139             auto search = recordMap.find(dbusIntfName);
140 
141             // PLDM FRU can have multiple records for the same FRU like General
142             // FRU record and multiple OEM FRU records. If the FRU item
143             // interface name is already in the map, that indicates a record
144             // info is already added for the FRU, so append the new record info
145             // to the same data.
146             if (search != recordMap.end())
147             {
148                 search->second.emplace_back(std::move(fruInfo));
149             }
150             else
151             {
152                 FruRecordInfos recordInfos{fruInfo};
153                 recordMap.emplace(dbusIntfName, recordInfos);
154             }
155         }
156         catch (const std::exception& e)
157         {
158             continue;
159         }
160     }
161 }
162 
163 } // namespace fru_parser
164 
165 } // namespace responder
166 
167 } // namespace pldm
168