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 "src.hpp"
17 
18 #include "device_callouts.hpp"
19 #include "json_utils.hpp"
20 #include "paths.hpp"
21 #include "pel_values.hpp"
22 #ifdef PELTOOL
23 #include <Python.h>
24 
25 #include <nlohmann/json.hpp>
26 
27 #include <sstream>
28 #endif
29 #include <fmt/format.h>
30 
31 #include <phosphor-logging/log.hpp>
32 
33 namespace openpower
34 {
35 namespace pels
36 {
37 namespace pv = openpower::pels::pel_values;
38 namespace rg = openpower::pels::message;
39 using namespace phosphor::logging;
40 using namespace std::string_literals;
41 
42 constexpr size_t ccinSize = 4;
43 
44 #ifdef PELTOOL
45 using orderedJSON = nlohmann::ordered_json;
46 
47 void pyDecRef(PyObject* pyObj)
48 {
49     Py_XDECREF(pyObj);
50 }
51 
52 /**
53  * @brief Returns a JSON string to append to SRC section.
54  *
55  * The returning string will contain a JSON object, but without
56  * the outer {}.  If the input JSON isn't a JSON object (dict), then
57  * one will be created with the input added to a 'SRC Details' key.
58  *
59  * @param[in] json - The JSON to convert to a string
60  *
61  * @return std::string - The JSON string
62  */
63 std::string prettyJSON(const orderedJSON& json)
64 {
65     orderedJSON output;
66     if (!json.is_object())
67     {
68         output["SRC Details"] = json;
69     }
70     else
71     {
72         for (const auto& [key, value] : json.items())
73         {
74             output["SRC Details"][key] = value;
75         }
76     }
77 
78     // Let nlohmann do the pretty printing.
79     std::stringstream stream;
80     stream << std::setw(4) << output;
81 
82     auto jsonString = stream.str();
83 
84     // Now it looks like:
85     // {
86     //     "Key": "Value",
87     //     ...
88     // }
89 
90     // Replace the { and the following newline, and the } and its
91     // preceeding newline.
92     jsonString.erase(0, 2);
93 
94     auto pos = jsonString.find_last_of('}');
95     jsonString.erase(pos - 1);
96 
97     return jsonString;
98 }
99 
100 /**
101  * @brief Call Python modules to parse the data into a JSON string
102  *
103  * The module to call is based on the Creator Subsystem ID under the namespace
104  * "srcparsers". For example: "srcparsers.xsrc.xsrc" where "x" is the Creator
105  * Subsystem ID in ASCII lowercase.
106  *
107  * All modules must provide the following:
108  * Function: parseSRCToJson
109  * Argument list:
110  *    1. (str) ASCII string (Hex Word 1)
111  *    2. (str) Hex Word 2
112  *    3. (str) Hex Word 3
113  *    4. (str) Hex Word 4
114  *    5. (str) Hex Word 5
115  *    6. (str) Hex Word 6
116  *    7. (str) Hex Word 7
117  *    8. (str) Hex Word 8
118  *    9. (str) Hex Word 9
119  *-Return data:
120  *    1. (str) JSON string
121  *
122  * @param[in] hexwords - Vector of strings of Hexwords 1-9
123  * @param[in] creatorID - The creatorID from the Private Header section
124  * @return std::optional<std::string> - The JSON string if it could be created,
125  *                                      else std::nullopt
126  */
127 std::optional<std::string> getPythonJSON(std::vector<std::string>& hexwords,
128                                          uint8_t creatorID)
129 {
130     PyObject *pName, *pModule, *eType, *eValue, *eTraceback;
131     std::string pErrStr;
132     std::string module = getNumberString("%c", tolower(creatorID)) + "src";
133     pName = PyUnicode_FromString(
134         std::string("srcparsers." + module + "." + module).c_str());
135     std::unique_ptr<PyObject, decltype(&pyDecRef)> modNamePtr(pName, &pyDecRef);
136     pModule = PyImport_Import(pName);
137     if (pModule == NULL)
138     {
139         pErrStr = "No error string found";
140         PyErr_Fetch(&eType, &eValue, &eTraceback);
141         if (eType)
142         {
143             Py_XDECREF(eType);
144         }
145         if (eTraceback)
146         {
147             Py_XDECREF(eTraceback);
148         }
149         if (eValue)
150         {
151             PyObject* pStr = PyObject_Str(eValue);
152             Py_XDECREF(eValue);
153             if (pStr)
154             {
155                 pErrStr = PyUnicode_AsUTF8(pStr);
156                 Py_XDECREF(pStr);
157             }
158         }
159     }
160     else
161     {
162         std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(pModule,
163                                                               &pyDecRef);
164         std::string funcToCall = "parseSRCToJson";
165         PyObject* pKey = PyUnicode_FromString(funcToCall.c_str());
166         std::unique_ptr<PyObject, decltype(&pyDecRef)> keyPtr(pKey, &pyDecRef);
167         PyObject* pDict = PyModule_GetDict(pModule);
168         Py_INCREF(pDict);
169         if (!PyDict_Contains(pDict, pKey))
170         {
171             Py_DECREF(pDict);
172             log<level::ERR>(
173                 "Python module error",
174                 entry("ERROR=%s",
175                       std::string(funcToCall + " function missing").c_str()),
176                 entry("SRC=%s", hexwords.front().c_str()),
177                 entry("PARSER_MODULE=%s", module.c_str()));
178             return std::nullopt;
179         }
180         PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
181         Py_DECREF(pDict);
182         Py_INCREF(pFunc);
183         if (PyCallable_Check(pFunc))
184         {
185             PyObject* pArgs = PyTuple_New(9);
186             std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(pArgs,
187                                                                   &pyDecRef);
188             for (size_t i = 0; i < 9; i++)
189             {
190                 std::string arg{"00000000"};
191                 if (i < hexwords.size())
192                 {
193                     arg = hexwords[i];
194                 }
195                 PyTuple_SetItem(pArgs, i, Py_BuildValue("s", arg.c_str()));
196             }
197             PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
198             Py_DECREF(pFunc);
199             if (pResult)
200             {
201                 std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
202                     pResult, &pyDecRef);
203                 PyObject* pBytes = PyUnicode_AsEncodedString(pResult, "utf-8",
204                                                              "~E~");
205                 std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
206                     pBytes, &pyDecRef);
207                 const char* output = PyBytes_AS_STRING(pBytes);
208                 try
209                 {
210                     orderedJSON json = orderedJSON::parse(output);
211                     if ((json.is_object() && !json.empty()) ||
212                         (json.is_array() && json.size() > 0) ||
213                         (json.is_string() && json != ""))
214                     {
215                         return prettyJSON(json);
216                     }
217                 }
218                 catch (const std::exception& e)
219                 {
220                     log<level::ERR>("Bad JSON from parser",
221                                     entry("ERROR=%s", e.what()),
222                                     entry("SRC=%s", hexwords.front().c_str()),
223                                     entry("PARSER_MODULE=%s", module.c_str()));
224                     return std::nullopt;
225                 }
226             }
227             else
228             {
229                 pErrStr = "No error string found";
230                 PyErr_Fetch(&eType, &eValue, &eTraceback);
231                 if (eType)
232                 {
233                     Py_XDECREF(eType);
234                 }
235                 if (eTraceback)
236                 {
237                     Py_XDECREF(eTraceback);
238                 }
239                 if (eValue)
240                 {
241                     PyObject* pStr = PyObject_Str(eValue);
242                     Py_XDECREF(eValue);
243                     if (pStr)
244                     {
245                         pErrStr = PyUnicode_AsUTF8(pStr);
246                         Py_XDECREF(pStr);
247                     }
248                 }
249             }
250         }
251     }
252     if (!pErrStr.empty())
253     {
254         log<level::DEBUG>("Python exception thrown by parser",
255                           entry("ERROR=%s", pErrStr.c_str()),
256                           entry("SRC=%s", hexwords.front().c_str()),
257                           entry("PARSER_MODULE=%s", module.c_str()));
258     }
259     return std::nullopt;
260 }
261 #endif
262 
263 void SRC::unflatten(Stream& stream)
264 {
265     stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
266         _reserved2B >> _size;
267 
268     for (auto& word : _hexData)
269     {
270         stream >> word;
271     }
272 
273     _asciiString = std::make_unique<src::AsciiString>(stream);
274 
275     if (hasAdditionalSections())
276     {
277         // The callouts section is currently the only extra subsection type
278         _callouts = std::make_unique<src::Callouts>(stream);
279     }
280 }
281 
282 void SRC::flatten(Stream& stream) const
283 {
284     stream << _header << _version << _flags << _reserved1B << _wordCount
285            << _reserved2B << _size;
286 
287     for (auto& word : _hexData)
288     {
289         stream << word;
290     }
291 
292     _asciiString->flatten(stream);
293 
294     if (_callouts)
295     {
296         _callouts->flatten(stream);
297     }
298 }
299 
300 SRC::SRC(Stream& pel)
301 {
302     try
303     {
304         unflatten(pel);
305         validate();
306     }
307     catch (const std::exception& e)
308     {
309         log<level::ERR>(
310             fmt::format("Cannot unflatten SRC: {}", e.what()).c_str());
311         _valid = false;
312     }
313 }
314 
315 SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
316          const nlohmann::json& jsonCallouts, const DataInterfaceBase& dataIface)
317 {
318     _header.id = static_cast<uint16_t>(SectionID::primarySRC);
319     _header.version = srcSectionVersion;
320     _header.subType = srcSectionSubtype;
321     _header.componentID = regEntry.componentID;
322 
323     _version = srcVersion;
324 
325     _flags = 0;
326 
327     _reserved1B = 0;
328 
329     _wordCount = numSRCHexDataWords + 1;
330 
331     _reserved2B = 0;
332 
333     // There are multiple fields encoded in the hex data words.
334     std::for_each(_hexData.begin(), _hexData.end(),
335                   [](auto& word) { word = 0; });
336 
337     // Hex Word 2 Nibbles:
338     //   MIGVEPFF
339     //   M: Partition dump status = 0
340     //   I: System boot state = TODO
341     //   G: Partition Boot type = 0
342     //   V: BMC dump status
343     //   E: Platform boot mode = 0 (side = temporary, speed = fast)
344     //   P: Platform dump status
345     //  FF: SRC format, set below
346 
347     setProgressCode(dataIface);
348     setDumpStatus(dataIface);
349     setBMCFormat();
350     setBMCPosition();
351     setMotherboardCCIN(dataIface);
352 
353     // Fill in the last 4 words from the AdditionalData property contents.
354     setUserDefinedHexWords(regEntry, additionalData);
355 
356     _asciiString = std::make_unique<src::AsciiString>(regEntry);
357 
358     // Check for additional data - PEL_SUBSYSTEM
359     auto ss = additionalData.getValue("PEL_SUBSYSTEM");
360     if (ss)
361     {
362         auto eventSubsystem = std::stoul(*ss, NULL, 16);
363         std::string subsystem = pv::getValue(eventSubsystem,
364                                              pel_values::subsystemValues);
365         if (subsystem == "invalid")
366         {
367             log<level::WARNING>(
368                 fmt::format("SRC: Invalid SubSystem value:{:#X}",
369                             eventSubsystem)
370                     .c_str());
371         }
372         else
373         {
374             _asciiString->setByte(2, eventSubsystem);
375         }
376     }
377 
378     addCallouts(regEntry, additionalData, jsonCallouts, dataIface);
379 
380     _size = baseSRCSize;
381     _size += _callouts ? _callouts->flattenedSize() : 0;
382     _header.size = Section::flattenedSize() + _size;
383 
384     _valid = true;
385 }
386 
387 void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
388                                  const AdditionalData& ad)
389 {
390     if (!regEntry.src.hexwordADFields)
391     {
392         return;
393     }
394 
395     // Save the AdditionalData value corresponding to the first element of
396     // adName tuple into _hexData[wordNum].
397     for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
398     {
399         // Can only set words 6 - 9
400         if (!isUserDefinedWord(wordNum))
401         {
402             std::string msg = "SRC user data word out of range: " +
403                               std::to_string(wordNum);
404             addDebugData(msg);
405             continue;
406         }
407 
408         auto value = ad.getValue(std::get<0>(adName));
409         if (value)
410         {
411             _hexData[getWordIndexFromWordNum(wordNum)] =
412                 std::strtoul(value.value().c_str(), nullptr, 0);
413         }
414         else
415         {
416             std::string msg = "Source for user data SRC word not found: " +
417                               std::get<0>(adName);
418             addDebugData(msg);
419         }
420     }
421 }
422 
423 void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
424 {
425     uint32_t ccin = 0;
426     auto ccinString = dataIface.getMotherboardCCIN();
427 
428     try
429     {
430         if (ccinString.size() == ccinSize)
431         {
432             ccin = std::stoi(ccinString, 0, 16);
433         }
434     }
435     catch (const std::exception& e)
436     {
437         log<level::WARNING>("Could not convert motherboard CCIN to a number",
438                             entry("CCIN=%s", ccinString.c_str()));
439         return;
440     }
441 
442     // Set the first 2 bytes
443     _hexData[1] |= ccin << 16;
444 }
445 
446 void SRC::validate()
447 {
448     bool failed = false;
449 
450     if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
451         (header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
452     {
453         log<level::ERR>(
454             fmt::format("Invalid SRC section ID: {0:#x}", header().id).c_str());
455         failed = true;
456     }
457 
458     // Check the version in the SRC, not in the header
459     if (_version != srcVersion)
460     {
461         log<level::ERR>(
462             fmt::format("Invalid SRC version: {0:#x}", header().version)
463                 .c_str());
464         failed = true;
465     }
466 
467     _valid = failed ? false : true;
468 }
469 
470 bool SRC::isBMCSRC() const
471 {
472     auto as = asciiString();
473     if (as.length() >= 2)
474     {
475         uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
476         return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
477                 errorType == static_cast<uint8_t>(SRCType::powerError));
478     }
479     return false;
480 }
481 
482 std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
483                                                 DetailLevel type,
484                                                 bool toCache) const
485 {
486     const std::string jsonIndent(indentLevel, 0x20);
487     std::string errorOut;
488     if (isBMCSRC())
489     {
490         auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
491                                      rg::LookupType::reasonCode, toCache);
492         if (entry)
493         {
494             errorOut.append(jsonIndent + "\"Error Details\": {\n");
495             auto errorMsg = getErrorMessage(*entry);
496             if (errorMsg)
497             {
498                 if (type == DetailLevel::message)
499                 {
500                     return errorMsg.value();
501                 }
502                 else
503                 {
504                     jsonInsert(errorOut, "Message", errorMsg.value(), 2);
505                 }
506             }
507             if (entry->src.hexwordADFields)
508             {
509                 std::map<size_t, std::tuple<std::string, std::string>>
510                     adFields = entry->src.hexwordADFields.value();
511                 for (const auto& hexwordMap : adFields)
512                 {
513                     auto srcValue = getNumberString(
514                         "0x%X",
515                         _hexData[getWordIndexFromWordNum(hexwordMap.first)]);
516 
517                     auto srcKey = std::get<0>(hexwordMap.second);
518                     auto srcDesc = std::get<1>(hexwordMap.second);
519 
520                     // Only include this hex word in the error details if the
521                     // description exists.
522                     if (!srcDesc.empty())
523                     {
524                         std::vector<std::string> valueDescr;
525                         valueDescr.push_back(srcValue);
526                         valueDescr.push_back(srcDesc);
527                         jsonInsertArray(errorOut, srcKey, valueDescr, 2);
528                     }
529                 }
530             }
531             errorOut.erase(errorOut.size() - 2);
532             errorOut.append("\n");
533             errorOut.append(jsonIndent + "},\n");
534             return errorOut;
535         }
536     }
537     return std::nullopt;
538 }
539 
540 std::optional<std::string>
541     SRC::getErrorMessage(const message::Entry& regEntry) const
542 {
543     try
544     {
545         if (regEntry.doc.messageArgSources)
546         {
547             std::vector<uint32_t> argSourceVals;
548             std::string message;
549             const auto& argValues = regEntry.doc.messageArgSources.value();
550             for (size_t i = 0; i < argValues.size(); ++i)
551             {
552                 argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
553                     argValues[i].back() - '0')]);
554             }
555 
556             auto it = std::begin(regEntry.doc.message);
557             auto it_end = std::end(regEntry.doc.message);
558 
559             while (it != it_end)
560             {
561                 if (*it == '%')
562                 {
563                     ++it;
564 
565                     size_t wordIndex = *it - '0';
566                     if (isdigit(*it) && wordIndex >= 1 &&
567                         static_cast<uint16_t>(wordIndex) <=
568                             argSourceVals.size())
569                     {
570                         message.append(getNumberString(
571                             "0x%08X", argSourceVals[wordIndex - 1]));
572                     }
573                     else
574                     {
575                         message.append("%" + std::string(1, *it));
576                     }
577                 }
578                 else
579                 {
580                     message.push_back(*it);
581                 }
582                 ++it;
583             }
584 
585             return message;
586         }
587         else
588         {
589             return regEntry.doc.message;
590         }
591     }
592     catch (const std::exception& e)
593     {
594         log<level::ERR>("Cannot get error message from registry entry",
595                         entry("ERROR=%s", e.what()));
596     }
597     return std::nullopt;
598 }
599 
600 std::optional<std::string> SRC::getCallouts() const
601 {
602     if (!_callouts)
603     {
604         return std::nullopt;
605     }
606     std::string printOut;
607     const std::string jsonIndent(indentLevel, 0x20);
608     const auto& callout = _callouts->callouts();
609     const auto& compDescrp = pv::failingComponentType;
610     printOut.append(jsonIndent + "\"Callout Section\": {\n");
611     jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
612     printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
613     for (auto& entry : callout)
614     {
615         printOut.append("{\n");
616         if (entry->fruIdentity())
617         {
618             jsonInsert(
619                 printOut, "FRU Type",
620                 compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
621             jsonInsert(printOut, "Priority",
622                        pv::getValue(entry->priority(),
623                                     pel_values::calloutPriorityValues),
624                        3);
625             if (!entry->locationCode().empty())
626             {
627                 jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
628             }
629             if (entry->fruIdentity()->getPN().has_value())
630             {
631                 jsonInsert(printOut, "Part Number",
632                            entry->fruIdentity()->getPN().value(), 3);
633             }
634             if (entry->fruIdentity()->getMaintProc().has_value())
635             {
636                 jsonInsert(printOut, "Procedure",
637                            entry->fruIdentity()->getMaintProc().value(), 3);
638                 if (pv::procedureDesc.find(
639                         entry->fruIdentity()->getMaintProc().value()) !=
640                     pv::procedureDesc.end())
641                 {
642                     jsonInsert(
643                         printOut, "Description",
644                         pv::procedureDesc.at(
645                             entry->fruIdentity()->getMaintProc().value()),
646                         3);
647                 }
648             }
649             if (entry->fruIdentity()->getCCIN().has_value())
650             {
651                 jsonInsert(printOut, "CCIN",
652                            entry->fruIdentity()->getCCIN().value(), 3);
653             }
654             if (entry->fruIdentity()->getSN().has_value())
655             {
656                 jsonInsert(printOut, "Serial Number",
657                            entry->fruIdentity()->getSN().value(), 3);
658             }
659         }
660         if (entry->pceIdentity())
661         {
662             const auto& pceIdentMtms = entry->pceIdentity()->mtms();
663             if (!pceIdentMtms.machineTypeAndModel().empty())
664             {
665                 jsonInsert(printOut, "PCE MTMS",
666                            pceIdentMtms.machineTypeAndModel() + "_" +
667                                pceIdentMtms.machineSerialNumber(),
668                            3);
669             }
670             if (!entry->pceIdentity()->enclosureName().empty())
671             {
672                 jsonInsert(printOut, "PCE Name",
673                            entry->pceIdentity()->enclosureName(), 3);
674             }
675         }
676         if (entry->mru())
677         {
678             const auto& mruCallouts = entry->mru()->mrus();
679             std::string mruId;
680             for (auto& element : mruCallouts)
681             {
682                 if (!mruId.empty())
683                 {
684                     mruId.append(", " + getNumberString("%08X", element.id));
685                 }
686                 else
687                 {
688                     mruId.append(getNumberString("%08X", element.id));
689                 }
690             }
691             jsonInsert(printOut, "MRU Id", mruId, 3);
692         }
693         printOut.erase(printOut.size() - 2);
694         printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
695     };
696     printOut.erase(printOut.size() - 2);
697     printOut.append("]\n" + jsonIndent + "}");
698     return printOut;
699 }
700 
701 std::optional<std::string> SRC::getJSON(message::Registry& registry,
702                                         const std::vector<std::string>& plugins
703                                         [[maybe_unused]],
704                                         uint8_t creatorID) const
705 {
706     std::string ps;
707     std::vector<std::string> hexwords;
708     jsonInsert(ps, pv::sectionVer, getNumberString("%d", _header.version), 1);
709     jsonInsert(ps, pv::subSection, getNumberString("%d", _header.subType), 1);
710     jsonInsert(ps, pv::createdBy,
711                getComponentName(_header.componentID, creatorID), 1);
712     jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
713     jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
714                1);
715     jsonInsert(ps, "Virtual Progress SRC",
716                pv::boolString.at(_flags & virtualProgressSRC), 1);
717     jsonInsert(ps, "I5/OS Service Event Bit",
718                pv::boolString.at(_flags & i5OSServiceEventBit), 1);
719     jsonInsert(ps, "Hypervisor Dump Initiated",
720                pv::boolString.at(_flags & hypDumpInit), 1);
721 
722     if (isBMCSRC())
723     {
724         std::string ccinString;
725         uint32_t ccin = _hexData[1] >> 16;
726 
727         if (ccin)
728         {
729             ccinString = getNumberString("%04X", ccin);
730         }
731         // The PEL spec calls it a backplane, so call it that here.
732         jsonInsert(ps, "Backplane CCIN", ccinString, 1);
733 
734         jsonInsert(ps, "Terminate FW Error",
735                    pv::boolString.at(
736                        _hexData[3] &
737                        static_cast<uint32_t>(ErrorStatusFlags::terminateFwErr)),
738                    1);
739         jsonInsert(ps, "Deconfigured",
740                    pv::boolString.at(
741                        _hexData[3] &
742                        static_cast<uint32_t>(ErrorStatusFlags::deconfigured)),
743                    1);
744 
745         jsonInsert(
746             ps, "Guarded",
747             pv::boolString.at(_hexData[3] &
748                               static_cast<uint32_t>(ErrorStatusFlags::guarded)),
749             1);
750     }
751 
752     auto errorDetails = getErrorDetails(registry, DetailLevel::json, true);
753     if (errorDetails)
754     {
755         ps.append(errorDetails.value());
756     }
757     jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
758                1);
759     std::string refcode = asciiString();
760     hexwords.push_back(refcode);
761     std::string extRefcode;
762     size_t pos = refcode.find(0x20);
763     if (pos != std::string::npos)
764     {
765         size_t nextPos = refcode.find_first_not_of(0x20, pos);
766         if (nextPos != std::string::npos)
767         {
768             extRefcode = trimEnd(refcode.substr(nextPos));
769         }
770         refcode.erase(pos);
771     }
772     jsonInsert(ps, "Reference Code", refcode, 1);
773     if (!extRefcode.empty())
774     {
775         jsonInsert(ps, "Extended Reference Code", extRefcode, 1);
776     }
777     for (size_t i = 2; i <= _wordCount; i++)
778     {
779         std::string tmpWord =
780             getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]);
781         jsonInsert(ps, "Hex Word " + std::to_string(i), tmpWord, 1);
782         hexwords.push_back(tmpWord);
783     }
784     auto calloutJson = getCallouts();
785     if (calloutJson)
786     {
787         ps.append(calloutJson.value());
788         ps.append(",\n");
789     }
790     std::string subsystem = getNumberString("%c", tolower(creatorID));
791     bool srcDetailExists = false;
792 #ifdef PELTOOL
793     if (std::find(plugins.begin(), plugins.end(), subsystem + "src") !=
794         plugins.end())
795     {
796         auto pyJson = getPythonJSON(hexwords, creatorID);
797         if (pyJson)
798         {
799             ps.append(pyJson.value());
800             srcDetailExists = true;
801         }
802     }
803 #endif
804     if (!srcDetailExists)
805     {
806         ps.erase(ps.size() - 2);
807     }
808     return ps;
809 }
810 
811 void SRC::addCallouts(const message::Entry& regEntry,
812                       const AdditionalData& additionalData,
813                       const nlohmann::json& jsonCallouts,
814                       const DataInterfaceBase& dataIface)
815 {
816     auto registryCallouts = getRegistryCallouts(regEntry, additionalData,
817                                                 dataIface);
818 
819     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
820     auto priority = additionalData.getValue("CALLOUT_PRIORITY");
821 
822     std::optional<CalloutPriority> calloutPriority;
823 
824     // Only  H, M or L priority values.
825     if (priority && !(*priority).empty())
826     {
827         uint8_t p = (*priority)[0];
828         if (p == 'H' || p == 'M' || p == 'L')
829         {
830             calloutPriority = static_cast<CalloutPriority>(p);
831         }
832     }
833     // If the first registry callout says to use the passed in inventory
834     // path to get the location code for a symbolic FRU callout with a
835     // trusted location code, then do not add the inventory path as a
836     // normal FRU callout.
837     bool useInvForSymbolicFRULocCode =
838         !registryCallouts.empty() && registryCallouts[0].useInventoryLocCode &&
839         !registryCallouts[0].symbolicFRUTrusted.empty();
840 
841     if (item && !useInvForSymbolicFRULocCode)
842     {
843         addInventoryCallout(*item, calloutPriority, std::nullopt, dataIface);
844     }
845 
846     addDevicePathCallouts(additionalData, dataIface);
847 
848     addRegistryCallouts(registryCallouts, dataIface,
849                         (useInvForSymbolicFRULocCode) ? item : std::nullopt);
850 
851     if (!jsonCallouts.empty())
852     {
853         addJSONCallouts(jsonCallouts, dataIface);
854     }
855 }
856 
857 void SRC::addInventoryCallout(const std::string& inventoryPath,
858                               const std::optional<CalloutPriority>& priority,
859                               const std::optional<std::string>& locationCode,
860                               const DataInterfaceBase& dataIface,
861                               const std::vector<src::MRU::MRUCallout>& mrus)
862 {
863     std::string locCode;
864     std::string fn;
865     std::string ccin;
866     std::string sn;
867     std::unique_ptr<src::Callout> callout;
868 
869     try
870     {
871         // Use the passed in location code if there otherwise look it up
872         if (locationCode)
873         {
874             locCode = *locationCode;
875         }
876         else
877         {
878             locCode = dataIface.getLocationCode(inventoryPath);
879         }
880 
881         try
882         {
883             dataIface.getHWCalloutFields(inventoryPath, fn, ccin, sn);
884 
885             CalloutPriority p = priority ? priority.value()
886                                          : CalloutPriority::high;
887 
888             callout = std::make_unique<src::Callout>(p, locCode, fn, ccin, sn,
889                                                      mrus);
890         }
891         catch (const sdbusplus::exception_t& e)
892         {
893             std::string msg = "No VPD found for " + inventoryPath + ": " +
894                               e.what();
895             addDebugData(msg);
896 
897             // Just create the callout with empty FRU fields
898             callout = std::make_unique<src::Callout>(
899                 CalloutPriority::high, locCode, fn, ccin, sn, mrus);
900         }
901     }
902     catch (const sdbusplus::exception_t& e)
903     {
904         std::string msg = "Could not get location code for " + inventoryPath +
905                           ": " + e.what();
906         addDebugData(msg);
907 
908         // Don't add a callout in this case, because:
909         // 1) With how the inventory is primed, there is no case where
910         //    a location code is expected to be missing.  This implies
911         //    the caller is passing in something invalid.
912         // 2) The addDebugData call above will put the passed in path into
913         //    a user data section that can be seen by development for debug.
914         // 3) Even if we wanted to do a 'no_vpd_for_fru' sort of maint.
915         //    procedure, we don't have a good way to indicate to the user
916         //    anything about the intended callout (they won't see user data).
917         // 4) Creating a new standalone event log for this problem isn't
918         //    possible from inside a PEL section.
919     }
920 
921     if (callout)
922     {
923         createCalloutsObject();
924         _callouts->addCallout(std::move(callout));
925     }
926 }
927 
928 std::vector<message::RegistryCallout>
929     SRC::getRegistryCallouts(const message::Entry& regEntry,
930                              const AdditionalData& additionalData,
931                              const DataInterfaceBase& dataIface)
932 {
933     std::vector<message::RegistryCallout> registryCallouts;
934 
935     if (regEntry.callouts)
936     {
937         std::vector<std::string> systemNames;
938 
939         try
940         {
941             systemNames = dataIface.getSystemNames();
942         }
943         catch (const std::exception& e)
944         {
945             // Compatible interface not available yet
946         }
947 
948         try
949         {
950             registryCallouts = message::Registry::getCallouts(
951                 regEntry.callouts.value(), systemNames, additionalData);
952         }
953         catch (const std::exception& e)
954         {
955             addDebugData(fmt::format(
956                 "Error parsing PEL message registry callout JSON: {}",
957                 e.what()));
958         }
959     }
960 
961     return registryCallouts;
962 }
963 
964 void SRC::addRegistryCallouts(
965     const std::vector<message::RegistryCallout>& callouts,
966     const DataInterfaceBase& dataIface,
967     std::optional<std::string> trustedSymbolicFRUInvPath)
968 {
969     try
970     {
971         for (const auto& callout : callouts)
972         {
973             addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
974 
975             // Only the first callout gets the inventory path
976             if (trustedSymbolicFRUInvPath)
977             {
978                 trustedSymbolicFRUInvPath = std::nullopt;
979             }
980         }
981     }
982     catch (const std::exception& e)
983     {
984         std::string msg = "Error parsing PEL message registry callout JSON: "s +
985                           e.what();
986         addDebugData(msg);
987     }
988 }
989 
990 void SRC::addRegistryCallout(
991     const message::RegistryCallout& regCallout,
992     const DataInterfaceBase& dataIface,
993     const std::optional<std::string>& trustedSymbolicFRUInvPath)
994 {
995     std::unique_ptr<src::Callout> callout;
996     auto locCode = regCallout.locCode;
997 
998     if (!locCode.empty())
999     {
1000         try
1001         {
1002             locCode = dataIface.expandLocationCode(locCode, 0);
1003         }
1004         catch (const std::exception& e)
1005         {
1006             auto msg = "Unable to expand location code " + locCode + ": " +
1007                        e.what();
1008             addDebugData(msg);
1009             return;
1010         }
1011     }
1012 
1013     // Via the PEL values table, get the priority enum.
1014     // The schema will have validated the priority was a valid value.
1015     auto priorityIt = pv::findByName(regCallout.priority,
1016                                      pv::calloutPriorityValues);
1017     assert(priorityIt != pv::calloutPriorityValues.end());
1018     auto priority =
1019         static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
1020 
1021     if (!regCallout.procedure.empty())
1022     {
1023         // Procedure callout
1024         callout = std::make_unique<src::Callout>(priority,
1025                                                  regCallout.procedure);
1026     }
1027     else if (!regCallout.symbolicFRU.empty())
1028     {
1029         // Symbolic FRU callout
1030         callout = std::make_unique<src::Callout>(
1031             priority, regCallout.symbolicFRU, locCode, false);
1032     }
1033     else if (!regCallout.symbolicFRUTrusted.empty())
1034     {
1035         // Symbolic FRU with trusted location code callout
1036 
1037         // Use the location code from the inventory path if there is one.
1038         if (trustedSymbolicFRUInvPath)
1039         {
1040             try
1041             {
1042                 locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
1043             }
1044             catch (const std::exception& e)
1045             {
1046                 addDebugData(
1047                     fmt::format("Could not get location code for {}: {}",
1048                                 *trustedSymbolicFRUInvPath, e.what()));
1049                 locCode.clear();
1050             }
1051         }
1052 
1053         // The registry wants it to be trusted, but that requires a valid
1054         // location code for it to actually be.
1055         callout = std::make_unique<src::Callout>(
1056             priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
1057     }
1058     else
1059     {
1060         // A hardware callout
1061         std::vector<std::string> inventoryPaths;
1062 
1063         try
1064         {
1065             // Get the inventory item from the unexpanded location code
1066             inventoryPaths =
1067                 dataIface.getInventoryFromLocCode(regCallout.locCode, 0, false);
1068         }
1069         catch (const std::exception& e)
1070         {
1071             std::string msg =
1072                 "Unable to get inventory path from location code: " + locCode +
1073                 ": " + e.what();
1074             addDebugData(msg);
1075             return;
1076         }
1077 
1078         // Just use first path returned since they all point to the same FRU.
1079         addInventoryCallout(inventoryPaths[0], priority, locCode, dataIface);
1080     }
1081 
1082     if (callout)
1083     {
1084         createCalloutsObject();
1085         _callouts->addCallout(std::move(callout));
1086     }
1087 }
1088 
1089 void SRC::addDevicePathCallouts(const AdditionalData& additionalData,
1090                                 const DataInterfaceBase& dataIface)
1091 {
1092     std::vector<device_callouts::Callout> callouts;
1093     auto i2cBus = additionalData.getValue("CALLOUT_IIC_BUS");
1094     auto i2cAddr = additionalData.getValue("CALLOUT_IIC_ADDR");
1095     auto devPath = additionalData.getValue("CALLOUT_DEVICE_PATH");
1096 
1097     // A device callout contains either:
1098     // * CALLOUT_ERRNO, CALLOUT_DEVICE_PATH
1099     // * CALLOUT_ERRNO, CALLOUT_IIC_BUS, CALLOUT_IIC_ADDR
1100     // We don't care about the errno.
1101 
1102     if (devPath)
1103     {
1104         try
1105         {
1106             callouts = device_callouts::getCallouts(*devPath,
1107                                                     dataIface.getSystemNames());
1108         }
1109         catch (const std::exception& e)
1110         {
1111             addDebugData(e.what());
1112             callouts.clear();
1113         }
1114     }
1115     else if (i2cBus && i2cAddr)
1116     {
1117         size_t bus;
1118         uint8_t address;
1119 
1120         try
1121         {
1122             // If /dev/i2c- is prepended, remove it
1123             if (i2cBus->find("/dev/i2c-") != std::string::npos)
1124             {
1125                 *i2cBus = i2cBus->substr(9);
1126             }
1127 
1128             bus = stoul(*i2cBus, nullptr, 0);
1129             address = stoul(*i2cAddr, nullptr, 0);
1130         }
1131         catch (const std::exception& e)
1132         {
1133             std::string msg = "Invalid CALLOUT_IIC_BUS " + *i2cBus +
1134                               " or CALLOUT_IIC_ADDR " + *i2cAddr +
1135                               " in AdditionalData property";
1136             addDebugData(msg);
1137             return;
1138         }
1139 
1140         try
1141         {
1142             callouts = device_callouts::getI2CCallouts(
1143                 bus, address, dataIface.getSystemNames());
1144         }
1145         catch (const std::exception& e)
1146         {
1147             addDebugData(e.what());
1148             callouts.clear();
1149         }
1150     }
1151 
1152     for (const auto& callout : callouts)
1153     {
1154         // The priority shouldn't be invalid, but check just in case.
1155         CalloutPriority priority = CalloutPriority::high;
1156 
1157         if (!callout.priority.empty())
1158         {
1159             auto p = pel_values::findByValue(
1160                 static_cast<uint32_t>(callout.priority[0]),
1161                 pel_values::calloutPriorityValues);
1162 
1163             if (p != pel_values::calloutPriorityValues.end())
1164             {
1165                 priority = static_cast<CalloutPriority>(callout.priority[0]);
1166             }
1167             else
1168             {
1169                 std::string msg =
1170                     "Invalid priority found in dev callout JSON: " +
1171                     callout.priority[0];
1172                 addDebugData(msg);
1173             }
1174         }
1175 
1176         std::optional<std::string> locCode;
1177 
1178         try
1179         {
1180             locCode = dataIface.expandLocationCode(callout.locationCode, 0);
1181         }
1182         catch (const std::exception& e)
1183         {
1184             auto msg = fmt::format("Unable to expand location code {}: {}",
1185                                    callout.locationCode, e.what());
1186             addDebugData(msg);
1187         }
1188 
1189         try
1190         {
1191             auto inventoryPaths = dataIface.getInventoryFromLocCode(
1192                 callout.locationCode, 0, false);
1193 
1194             // Just use first path returned since they all
1195             // point to the same FRU.
1196             addInventoryCallout(inventoryPaths[0], priority, locCode,
1197                                 dataIface);
1198         }
1199         catch (const std::exception& e)
1200         {
1201             std::string msg =
1202                 "Unable to get inventory path from location code: " +
1203                 callout.locationCode + ": " + e.what();
1204             addDebugData(msg);
1205         }
1206 
1207         // Until the code is there to convert these MRU value strings to
1208         // the official MRU values in the callout objects, just store
1209         // the MRU name in the debug UserData section.
1210         if (!callout.mru.empty())
1211         {
1212             std::string msg = "MRU: " + callout.mru;
1213             addDebugData(msg);
1214         }
1215 
1216         // getCallouts() may have generated some debug data it stored
1217         // in a callout object.  Save it as well.
1218         if (!callout.debug.empty())
1219         {
1220             addDebugData(callout.debug);
1221         }
1222     }
1223 }
1224 
1225 void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
1226                           const DataInterfaceBase& dataIface)
1227 {
1228     if (jsonCallouts.empty())
1229     {
1230         return;
1231     }
1232 
1233     if (!jsonCallouts.is_array())
1234     {
1235         addDebugData("Callout JSON isn't an array");
1236         return;
1237     }
1238 
1239     for (const auto& callout : jsonCallouts)
1240     {
1241         try
1242         {
1243             addJSONCallout(callout, dataIface);
1244         }
1245         catch (const std::exception& e)
1246         {
1247             addDebugData(fmt::format(
1248                 "Failed extracting callout data from JSON: {}", e.what()));
1249         }
1250     }
1251 }
1252 
1253 void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
1254                          const DataInterfaceBase& dataIface)
1255 {
1256     auto priority = getPriorityFromJSON(jsonCallout);
1257     std::string locCode;
1258     std::string unexpandedLocCode;
1259     std::unique_ptr<src::Callout> callout;
1260 
1261     // Expand the location code if it's there
1262     if (jsonCallout.contains("LocationCode"))
1263     {
1264         unexpandedLocCode = jsonCallout.at("LocationCode").get<std::string>();
1265 
1266         try
1267         {
1268             locCode = dataIface.expandLocationCode(unexpandedLocCode, 0);
1269         }
1270         catch (const std::exception& e)
1271         {
1272             addDebugData(fmt::format("Unable to expand location code {}: {}",
1273                                      unexpandedLocCode, e.what()));
1274             // Use the value from the JSON so at least there's something
1275             locCode = unexpandedLocCode;
1276         }
1277     }
1278 
1279     // Create either a procedure, symbolic FRU, or normal FRU callout.
1280     if (jsonCallout.contains("Procedure"))
1281     {
1282         auto procedure = jsonCallout.at("Procedure").get<std::string>();
1283 
1284         // If it's the registry name instead of the raw name, convert.
1285         if (pv::maintenanceProcedures.find(procedure) !=
1286             pv::maintenanceProcedures.end())
1287         {
1288             procedure = pv::maintenanceProcedures.at(procedure);
1289         }
1290 
1291         callout = std::make_unique<src::Callout>(
1292             static_cast<CalloutPriority>(priority), procedure,
1293             src::CalloutValueType::raw);
1294     }
1295     else if (jsonCallout.contains("SymbolicFRU"))
1296     {
1297         auto fru = jsonCallout.at("SymbolicFRU").get<std::string>();
1298 
1299         // If it's the registry name instead of the raw name, convert.
1300         if (pv::symbolicFRUs.find(fru) != pv::symbolicFRUs.end())
1301         {
1302             fru = pv::symbolicFRUs.at(fru);
1303         }
1304 
1305         bool trusted = false;
1306         if (jsonCallout.contains("TrustedLocationCode") && !locCode.empty())
1307         {
1308             trusted = jsonCallout.at("TrustedLocationCode").get<bool>();
1309         }
1310 
1311         callout = std::make_unique<src::Callout>(
1312             static_cast<CalloutPriority>(priority), fru,
1313             src::CalloutValueType::raw, locCode, trusted);
1314     }
1315     else
1316     {
1317         // A hardware FRU
1318         std::string inventoryPath;
1319         std::vector<src::MRU::MRUCallout> mrus;
1320 
1321         if (jsonCallout.contains("InventoryPath"))
1322         {
1323             inventoryPath = jsonCallout.at("InventoryPath").get<std::string>();
1324         }
1325         else
1326         {
1327             if (unexpandedLocCode.empty())
1328             {
1329                 throw std::runtime_error{"JSON callout needs either an "
1330                                          "inventory path or location code"};
1331             }
1332 
1333             try
1334             {
1335                 auto inventoryPaths = dataIface.getInventoryFromLocCode(
1336                     unexpandedLocCode, 0, false);
1337                 // Just use first path returned since they all
1338                 // point to the same FRU.
1339                 inventoryPath = inventoryPaths[0];
1340             }
1341             catch (const std::exception& e)
1342             {
1343                 throw std::runtime_error{
1344                     fmt::format("Unable to get inventory path from "
1345                                 "location code: {}: {}",
1346                                 unexpandedLocCode, e.what())};
1347             }
1348         }
1349 
1350         if (jsonCallout.contains("MRUs"))
1351         {
1352             mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
1353         }
1354 
1355         // If the location code was also passed in, use that here too
1356         // so addInventoryCallout doesn't have to look it up.
1357         std::optional<std::string> lc;
1358         if (!locCode.empty())
1359         {
1360             lc = locCode;
1361         }
1362 
1363         addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
1364 
1365         if (jsonCallout.contains("Deconfigured"))
1366         {
1367             if (jsonCallout.at("Deconfigured").get<bool>())
1368             {
1369                 setErrorStatusFlag(ErrorStatusFlags::deconfigured);
1370             }
1371         }
1372 
1373         if (jsonCallout.contains("Guarded"))
1374         {
1375             if (jsonCallout.at("Guarded").get<bool>())
1376             {
1377                 setErrorStatusFlag(ErrorStatusFlags::guarded);
1378             }
1379         }
1380     }
1381 
1382     if (callout)
1383     {
1384         createCalloutsObject();
1385         _callouts->addCallout(std::move(callout));
1386     }
1387 }
1388 
1389 CalloutPriority SRC::getPriorityFromJSON(const nlohmann::json& json)
1390 {
1391     // Looks like:
1392     // {
1393     //     "Priority": "H"
1394     // }
1395     auto p = json.at("Priority").get<std::string>();
1396     if (p.empty())
1397     {
1398         throw std::runtime_error{"Priority field in callout is empty"};
1399     }
1400 
1401     auto priority = static_cast<CalloutPriority>(p.front());
1402 
1403     // Validate it
1404     auto priorityIt = pv::findByValue(static_cast<uint32_t>(priority),
1405                                       pv::calloutPriorityValues);
1406     if (priorityIt == pv::calloutPriorityValues.end())
1407     {
1408         throw std::runtime_error{
1409             fmt::format("Invalid priority '{}' found in JSON callout", p)};
1410     }
1411 
1412     return priority;
1413 }
1414 
1415 std::vector<src::MRU::MRUCallout>
1416     SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
1417 {
1418     std::vector<src::MRU::MRUCallout> mrus;
1419 
1420     // Looks like:
1421     // [
1422     //     {
1423     //         "ID": 100,
1424     //         "Priority": "H"
1425     //     }
1426     // ]
1427     if (!mruJSON.is_array())
1428     {
1429         addDebugData("MRU callout JSON is not an array");
1430         return mrus;
1431     }
1432 
1433     for (const auto& mruCallout : mruJSON)
1434     {
1435         try
1436         {
1437             auto priority = getPriorityFromJSON(mruCallout);
1438             auto id = mruCallout.at("ID").get<uint32_t>();
1439 
1440             src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
1441             mrus.push_back(std::move(mru));
1442         }
1443         catch (const std::exception& e)
1444         {
1445             addDebugData(fmt::format("Invalid MRU entry in JSON: {}: {}",
1446                                      mruCallout.dump(), e.what()));
1447         }
1448     }
1449 
1450     return mrus;
1451 }
1452 
1453 void SRC::setDumpStatus(const DataInterfaceBase& dataIface)
1454 {
1455     std::vector<bool> dumpStatus{false, false, false};
1456 
1457     try
1458     {
1459         std::vector<std::string> dumpType = {"bmc/entry", "resource/entry",
1460                                              "system/entry"};
1461         dumpStatus = dataIface.checkDumpStatus(dumpType);
1462 
1463         // For bmc      - set bit 0 of nibble [4-7] bits of byte-1 SP dump
1464         // For resource - set bit 2 of nibble [4-7] bits of byte-2 Hypervisor
1465         // For system   - set bit 1 of nibble [4-7] bits of byte-2 HW dump
1466         _hexData[0] |= ((dumpStatus[0] << 19) | (dumpStatus[1] << 9) |
1467                         (dumpStatus[2] << 10));
1468     }
1469     catch (const std::exception& e)
1470     {
1471         log<level::ERR>(
1472             fmt::format("Checking dump status failed: {}", e.what()).c_str());
1473     }
1474 }
1475 
1476 std::vector<uint8_t> SRC::getSrcStruct()
1477 {
1478     std::vector<uint8_t> data;
1479     Stream stream{data};
1480 
1481     //------ Ref section 4.3 in PEL doc---
1482     //------ SRC Structure 40 bytes-------
1483     // Byte-0 | Byte-1 | Byte-2 | Byte-3 |
1484     // -----------------------------------
1485     //   02   |   08   |   00   |   09   | ==> Header
1486     //   00   |   00   |   00   |   48   | ==> Header
1487     //   00   |   00   |   00   |   00   | ==> Hex data word-2
1488     //   00   |   00   |   00   |   00   | ==> Hex data word-3
1489     //   00   |   00   |   00   |   00   | ==> Hex data word-4
1490     //   20   |   00   |   00   |   00   | ==> Hex data word-5
1491     //   00   |   00   |   00   |   00   | ==> Hex data word-6
1492     //   00   |   00   |   00   |   00   | ==> Hex data word-7
1493     //   00   |   00   |   00   |   00   | ==> Hex data word-8
1494     //   00   |   00   |   00   |   00   | ==> Hex data word-9
1495     // -----------------------------------
1496     //   ASCII string - 8 bytes          |
1497     // -----------------------------------
1498     //   ASCII space NULL - 24 bytes     |
1499     // -----------------------------------
1500     //_size = Base SRC struct: 8 byte header + hex data section + ASCII string
1501 
1502     uint8_t flags = (_flags | postOPPanel);
1503 
1504     stream << _version << flags << _reserved1B << _wordCount << _reserved2B
1505            << _size;
1506 
1507     for (auto& word : _hexData)
1508     {
1509         stream << word;
1510     }
1511 
1512     _asciiString->flatten(stream);
1513 
1514     return data;
1515 }
1516 
1517 void SRC::setProgressCode(const DataInterfaceBase& dataIface)
1518 {
1519     std::vector<uint8_t> progressSRC;
1520 
1521     try
1522     {
1523         progressSRC = dataIface.getRawProgressSRC();
1524     }
1525     catch (const std::exception& e)
1526     {
1527         log<level::ERR>(
1528             fmt::format("Error getting progress code: {}", e.what()).c_str());
1529         return;
1530     }
1531 
1532     _hexData[2] = getProgressCode(progressSRC);
1533 }
1534 
1535 uint32_t SRC::getProgressCode(std::vector<uint8_t>& rawProgressSRC)
1536 {
1537     uint32_t progressCode = 0;
1538 
1539     // A valid progress SRC is at least 72 bytes
1540     if (rawProgressSRC.size() < 72)
1541     {
1542         return progressCode;
1543     }
1544 
1545     try
1546     {
1547         // The ASCII string field in progress SRCs starts at offset 40.
1548         // Take the first 8 characters to put in the uint32:
1549         //   "CC009189" -> 0xCC009189
1550         Stream stream{rawProgressSRC, 40};
1551         src::AsciiString aString{stream};
1552         auto progressCodeString = aString.get().substr(0, 8);
1553 
1554         if (std::all_of(progressCodeString.begin(), progressCodeString.end(),
1555                         [](char c) {
1556                             return std::isxdigit(static_cast<unsigned char>(c));
1557                         }))
1558         {
1559             progressCode = std::stoul(progressCodeString, nullptr, 16);
1560         }
1561     }
1562     catch (const std::exception& e)
1563     {}
1564 
1565     return progressCode;
1566 }
1567 
1568 } // namespace pels
1569 } // namespace openpower
1570