1 /**
2  * Copyright © 2019 IBM 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 #include "pel.hpp"
17 
18 #include "bcd_time.hpp"
19 #include "extended_user_header.hpp"
20 #include "failing_mtms.hpp"
21 #include "json_utils.hpp"
22 #include "log_id.hpp"
23 #include "pel_rules.hpp"
24 #include "pel_values.hpp"
25 #include "section_factory.hpp"
26 #include "src.hpp"
27 #include "stream.hpp"
28 #include "user_data_formats.hpp"
29 
30 #include <iostream>
31 #include <phosphor-logging/log.hpp>
32 
33 namespace openpower
34 {
35 namespace pels
36 {
37 namespace message = openpower::pels::message;
38 namespace pv = openpower::pels::pel_values;
39 
40 constexpr auto unknownValue = "Unknown";
41 
42 PEL::PEL(const message::Entry& entry, uint32_t obmcLogID, uint64_t timestamp,
43          phosphor::logging::Entry::Level severity,
44          const AdditionalData& additionalData,
45          const DataInterfaceBase& dataIface)
46 {
47     _ph = std::make_unique<PrivateHeader>(entry.componentID, obmcLogID,
48                                           timestamp);
49     _uh = std::make_unique<UserHeader>(entry, severity);
50 
51     auto src = std::make_unique<SRC>(entry, additionalData, dataIface);
52 
53     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, entry, *src);
54 
55     _optionalSections.push_back(std::move(src));
56     _optionalSections.push_back(std::move(euh));
57 
58     auto mtms = std::make_unique<FailingMTMS>(dataIface);
59     _optionalSections.push_back(std::move(mtms));
60 
61     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
62     _optionalSections.push_back(std::move(ud));
63 
64     if (!additionalData.empty())
65     {
66         ud = util::makeADUserDataSection(additionalData);
67 
68         // To be safe, check there isn't too much data
69         if (size() + ud->header().size <= _maxPELSize)
70         {
71             _optionalSections.push_back(std::move(ud));
72         }
73     }
74 
75     _ph->setSectionCount(2 + _optionalSections.size());
76 
77     checkRulesAndFix();
78 }
79 
80 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0)
81 {
82 }
83 
84 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
85 {
86     populateFromRawData(data, obmcLogID);
87 }
88 
89 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
90 {
91     Stream pelData{data};
92     _ph = std::make_unique<PrivateHeader>(pelData);
93     if (obmcLogID != 0)
94     {
95         _ph->setOBMCLogID(obmcLogID);
96     }
97 
98     _uh = std::make_unique<UserHeader>(pelData);
99 
100     // Use the section factory to create the rest of the objects
101     for (size_t i = 2; i < _ph->sectionCount(); i++)
102     {
103         auto section = section_factory::create(pelData);
104         _optionalSections.push_back(std::move(section));
105     }
106 }
107 
108 bool PEL::valid() const
109 {
110     bool valid = _ph->valid();
111 
112     if (valid)
113     {
114         valid = _uh->valid();
115     }
116 
117     if (valid)
118     {
119         if (!std::all_of(_optionalSections.begin(), _optionalSections.end(),
120                          [](const auto& section) { return section->valid(); }))
121         {
122             valid = false;
123         }
124     }
125 
126     return valid;
127 }
128 
129 void PEL::setCommitTime()
130 {
131     auto now = std::chrono::system_clock::now();
132     _ph->setCommitTimestamp(getBCDTime(now));
133 }
134 
135 void PEL::assignID()
136 {
137     _ph->setID(generatePELID());
138 }
139 
140 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
141 {
142     Stream pelData{pelBuffer};
143 
144     if (!valid())
145     {
146         using namespace phosphor::logging;
147         log<level::WARNING>("Unflattening an invalid PEL");
148     }
149 
150     _ph->flatten(pelData);
151     _uh->flatten(pelData);
152 
153     for (auto& section : _optionalSections)
154     {
155         section->flatten(pelData);
156     }
157 }
158 
159 std::vector<uint8_t> PEL::data() const
160 {
161     std::vector<uint8_t> pelData;
162     flatten(pelData);
163     return pelData;
164 }
165 
166 size_t PEL::size() const
167 {
168     size_t size = 0;
169 
170     if (_ph)
171     {
172         size += _ph->header().size;
173     }
174 
175     if (_uh)
176     {
177         size += _uh->header().size;
178     }
179 
180     for (const auto& section : _optionalSections)
181     {
182         size += section->header().size;
183     }
184 
185     return size;
186 }
187 
188 std::optional<SRC*> PEL::primarySRC() const
189 {
190     auto src = std::find_if(
191         _optionalSections.begin(), _optionalSections.end(), [](auto& section) {
192             return section->header().id ==
193                    static_cast<uint16_t>(SectionID::primarySRC);
194         });
195     if (src != _optionalSections.end())
196     {
197         return static_cast<SRC*>(src->get());
198     }
199 
200     return std::nullopt;
201 }
202 
203 void PEL::checkRulesAndFix()
204 {
205     auto [actionFlags, eventType] =
206         pel_rules::check(_uh->actionFlags(), _uh->eventType(), _uh->severity());
207 
208     _uh->setActionFlags(actionFlags);
209     _uh->setEventType(eventType);
210 }
211 
212 void PEL::printSectionInJSON(const Section& section, std::string& buf,
213                              std::map<uint16_t, size_t>& pluralSections,
214                              message::Registry& registry) const
215 {
216     char tmpB[5];
217     uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
218                     static_cast<uint8_t>(section.header().id)};
219     sprintf(tmpB, "%c%c", id[0], id[1]);
220     std::string sectionID(tmpB);
221     std::string sectionName = pv::sectionTitles.count(sectionID)
222                                   ? pv::sectionTitles.at(sectionID)
223                                   : "Unknown Section";
224 
225     // Add a count if there are multiple of this type of section
226     auto count = pluralSections.find(section.header().id);
227     if (count != pluralSections.end())
228     {
229         sectionName += " " + std::to_string(count->second);
230         count->second++;
231     }
232 
233     if (section.valid())
234     {
235         auto json = (sectionID == "PS" || sectionID == "SS")
236                         ? section.getJSON(registry)
237                         : section.getJSON();
238         if (json)
239         {
240             buf += "\"" + sectionName + "\": {\n";
241             buf += *json + "\n},\n";
242         }
243         else
244         {
245             buf += "\"" + sectionName + "\": [\n";
246             std::vector<uint8_t> data;
247             Stream s{data};
248             section.flatten(s);
249             std::string dstr = dumpHex(std::data(data), data.size());
250             buf += dstr + "],\n";
251         }
252     }
253     else
254     {
255         buf += "\n\"Invalid Section\": [\n    \"invalid\"\n],\n";
256     }
257 }
258 
259 std::map<uint16_t, size_t> PEL::getPluralSections() const
260 {
261     std::map<uint16_t, size_t> sectionCounts;
262 
263     for (const auto& section : optionalSections())
264     {
265         if (sectionCounts.find(section->header().id) == sectionCounts.end())
266         {
267             sectionCounts[section->header().id] = 1;
268         }
269         else
270         {
271             sectionCounts[section->header().id]++;
272         }
273     }
274 
275     std::map<uint16_t, size_t> sections;
276     for (const auto& [id, count] : sectionCounts)
277     {
278         if (count > 1)
279         {
280             // Start with 0 here and printSectionInJSON()
281             // will increment it as it goes.
282             sections.emplace(id, 0);
283         }
284     }
285 
286     return sections;
287 }
288 
289 void PEL::toJSON(message::Registry& registry) const
290 {
291     auto sections = getPluralSections();
292 
293     std::string buf = "{\n";
294     printSectionInJSON(*(_ph.get()), buf, sections, registry);
295     printSectionInJSON(*(_uh.get()), buf, sections, registry);
296     for (auto& section : this->optionalSections())
297     {
298         printSectionInJSON(*(section.get()), buf, sections, registry);
299     }
300     buf += "}";
301     std::size_t found = buf.rfind(",");
302     if (found != std::string::npos)
303         buf.replace(found, 1, "");
304     std::cout << buf << std::endl;
305 }
306 
307 namespace util
308 {
309 
310 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
311 {
312     auto jsonString = json.dump();
313     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
314 
315     // Pad to a 4 byte boundary
316     while ((jsonData.size() % 4) != 0)
317     {
318         jsonData.push_back(0);
319     }
320 
321     return std::make_unique<UserData>(
322         static_cast<uint16_t>(ComponentID::phosphorLogging),
323         static_cast<uint8_t>(UserDataFormat::json),
324         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
325 }
326 
327 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
328 {
329     assert(!ad.empty());
330     nlohmann::json json;
331 
332     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
333     if (ad.getValue("ESEL"))
334     {
335         auto newAD = ad;
336         newAD.remove("ESEL");
337         json = newAD.toJSON();
338     }
339     else
340     {
341         json = ad.toJSON();
342     }
343 
344     return makeJSONUserDataSection(json);
345 }
346 
347 void addProcessNameToJSON(nlohmann::json& json,
348                           const std::optional<std::string>& pid,
349                           const DataInterfaceBase& dataIface)
350 {
351     std::string name{unknownValue};
352 
353     try
354     {
355         if (pid)
356         {
357             auto n = dataIface.getProcessName(*pid);
358             if (n)
359             {
360                 name = *n;
361             }
362         }
363     }
364     catch (std::exception& e)
365     {
366     }
367 
368     json["Process Name"] = std::move(name);
369 }
370 
371 void addBMCFWVersionIDToJSON(nlohmann::json& json,
372                              const DataInterfaceBase& dataIface)
373 {
374     auto id = dataIface.getBMCFWVersionID();
375     if (id.empty())
376     {
377         id = unknownValue;
378     }
379 
380     json["BMC Version ID"] = std::move(id);
381 }
382 
383 std::string lastSegment(char separator, std::string data)
384 {
385     auto pos = data.find_last_of(separator);
386     if (pos != std::string::npos)
387     {
388         data = data.substr(pos + 1);
389     }
390 
391     return data;
392 }
393 
394 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
395 {
396     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
397     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
398     json["HostState"] = lastSegment('.', dataIface.getHostState());
399 }
400 
401 std::unique_ptr<UserData>
402     makeSysInfoUserDataSection(const AdditionalData& ad,
403                                const DataInterfaceBase& dataIface)
404 {
405     nlohmann::json json;
406 
407     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
408     addBMCFWVersionIDToJSON(json, dataIface);
409     addStatesToJSON(json, dataIface);
410 
411     return makeJSONUserDataSection(json);
412 }
413 
414 } // namespace util
415 
416 } // namespace pels
417 } // namespace openpower
418