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     if (regEntry.src.checkstopFlag)
354     {
355         setErrorStatusFlag(ErrorStatusFlags::hwCheckstop);
356     }
357 
358     if (regEntry.src.deconfigFlag)
359     {
360         setErrorStatusFlag(ErrorStatusFlags::deconfigured);
361     }
362 
363     // Fill in the last 4 words from the AdditionalData property contents.
364     setUserDefinedHexWords(regEntry, additionalData);
365 
366     _asciiString = std::make_unique<src::AsciiString>(regEntry);
367 
368     // Check for additional data - PEL_SUBSYSTEM
369     auto ss = additionalData.getValue("PEL_SUBSYSTEM");
370     if (ss)
371     {
372         auto eventSubsystem = std::stoul(*ss, NULL, 16);
373         std::string subsystem = pv::getValue(eventSubsystem,
374                                              pel_values::subsystemValues);
375         if (subsystem == "invalid")
376         {
377             log<level::WARNING>(
378                 fmt::format("SRC: Invalid SubSystem value:{:#X}",
379                             eventSubsystem)
380                     .c_str());
381         }
382         else
383         {
384             _asciiString->setByte(2, eventSubsystem);
385         }
386     }
387 
388     addCallouts(regEntry, additionalData, jsonCallouts, dataIface);
389 
390     _size = baseSRCSize;
391     _size += _callouts ? _callouts->flattenedSize() : 0;
392     _header.size = Section::flattenedSize() + _size;
393 
394     _valid = true;
395 }
396 
397 void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
398                                  const AdditionalData& ad)
399 {
400     if (!regEntry.src.hexwordADFields)
401     {
402         return;
403     }
404 
405     // Save the AdditionalData value corresponding to the first element of
406     // adName tuple into _hexData[wordNum].
407     for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
408     {
409         // Can only set words 6 - 9
410         if (!isUserDefinedWord(wordNum))
411         {
412             std::string msg = "SRC user data word out of range: " +
413                               std::to_string(wordNum);
414             addDebugData(msg);
415             continue;
416         }
417 
418         auto value = ad.getValue(std::get<0>(adName));
419         if (value)
420         {
421             _hexData[getWordIndexFromWordNum(wordNum)] =
422                 std::strtoul(value.value().c_str(), nullptr, 0);
423         }
424         else
425         {
426             std::string msg = "Source for user data SRC word not found: " +
427                               std::get<0>(adName);
428             addDebugData(msg);
429         }
430     }
431 }
432 
433 void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
434 {
435     uint32_t ccin = 0;
436     auto ccinString = dataIface.getMotherboardCCIN();
437 
438     try
439     {
440         if (ccinString.size() == ccinSize)
441         {
442             ccin = std::stoi(ccinString, 0, 16);
443         }
444     }
445     catch (const std::exception& e)
446     {
447         log<level::WARNING>("Could not convert motherboard CCIN to a number",
448                             entry("CCIN=%s", ccinString.c_str()));
449         return;
450     }
451 
452     // Set the first 2 bytes
453     _hexData[1] |= ccin << 16;
454 }
455 
456 void SRC::validate()
457 {
458     bool failed = false;
459 
460     if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
461         (header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
462     {
463         log<level::ERR>(
464             fmt::format("Invalid SRC section ID: {0:#x}", header().id).c_str());
465         failed = true;
466     }
467 
468     // Check the version in the SRC, not in the header
469     if (_version != srcVersion)
470     {
471         log<level::ERR>(
472             fmt::format("Invalid SRC version: {0:#x}", header().version)
473                 .c_str());
474         failed = true;
475     }
476 
477     _valid = failed ? false : true;
478 }
479 
480 bool SRC::isBMCSRC() const
481 {
482     auto as = asciiString();
483     if (as.length() >= 2)
484     {
485         uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
486         return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
487                 errorType == static_cast<uint8_t>(SRCType::powerError));
488     }
489     return false;
490 }
491 
492 bool SRC::isHostbootSRC() const
493 {
494     auto as = asciiString();
495     if (as.length() >= 2)
496     {
497         uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
498         return errorType == static_cast<uint8_t>(SRCType::hostbootError);
499     }
500     return false;
501 }
502 
503 std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
504                                                 DetailLevel type,
505                                                 bool toCache) const
506 {
507     const std::string jsonIndent(indentLevel, 0x20);
508     std::string errorOut;
509     if (isBMCSRC())
510     {
511         auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
512                                      rg::LookupType::reasonCode, toCache);
513         if (entry)
514         {
515             errorOut.append(jsonIndent + "\"Error Details\": {\n");
516             auto errorMsg = getErrorMessage(*entry);
517             if (errorMsg)
518             {
519                 if (type == DetailLevel::message)
520                 {
521                     return errorMsg.value();
522                 }
523                 else
524                 {
525                     jsonInsert(errorOut, "Message", errorMsg.value(), 2);
526                 }
527             }
528             if (entry->src.hexwordADFields)
529             {
530                 std::map<size_t, std::tuple<std::string, std::string>>
531                     adFields = entry->src.hexwordADFields.value();
532                 for (const auto& hexwordMap : adFields)
533                 {
534                     auto srcValue = getNumberString(
535                         "0x%X",
536                         _hexData[getWordIndexFromWordNum(hexwordMap.first)]);
537 
538                     auto srcKey = std::get<0>(hexwordMap.second);
539                     auto srcDesc = std::get<1>(hexwordMap.second);
540 
541                     // Only include this hex word in the error details if the
542                     // description exists.
543                     if (!srcDesc.empty())
544                     {
545                         std::vector<std::string> valueDescr;
546                         valueDescr.push_back(srcValue);
547                         valueDescr.push_back(srcDesc);
548                         jsonInsertArray(errorOut, srcKey, valueDescr, 2);
549                     }
550                 }
551             }
552             errorOut.erase(errorOut.size() - 2);
553             errorOut.append("\n");
554             errorOut.append(jsonIndent + "},\n");
555             return errorOut;
556         }
557     }
558     return std::nullopt;
559 }
560 
561 std::optional<std::string>
562     SRC::getErrorMessage(const message::Entry& regEntry) const
563 {
564     try
565     {
566         if (regEntry.doc.messageArgSources)
567         {
568             std::vector<uint32_t> argSourceVals;
569             std::string message;
570             const auto& argValues = regEntry.doc.messageArgSources.value();
571             for (size_t i = 0; i < argValues.size(); ++i)
572             {
573                 argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
574                     argValues[i].back() - '0')]);
575             }
576 
577             auto it = std::begin(regEntry.doc.message);
578             auto it_end = std::end(regEntry.doc.message);
579 
580             while (it != it_end)
581             {
582                 if (*it == '%')
583                 {
584                     ++it;
585 
586                     size_t wordIndex = *it - '0';
587                     if (isdigit(*it) && wordIndex >= 1 &&
588                         static_cast<uint16_t>(wordIndex) <=
589                             argSourceVals.size())
590                     {
591                         message.append(getNumberString(
592                             "0x%08X", argSourceVals[wordIndex - 1]));
593                     }
594                     else
595                     {
596                         message.append("%" + std::string(1, *it));
597                     }
598                 }
599                 else
600                 {
601                     message.push_back(*it);
602                 }
603                 ++it;
604             }
605 
606             return message;
607         }
608         else
609         {
610             return regEntry.doc.message;
611         }
612     }
613     catch (const std::exception& e)
614     {
615         log<level::ERR>("Cannot get error message from registry entry",
616                         entry("ERROR=%s", e.what()));
617     }
618     return std::nullopt;
619 }
620 
621 std::optional<std::string> SRC::getCallouts() const
622 {
623     if (!_callouts)
624     {
625         return std::nullopt;
626     }
627     std::string printOut;
628     const std::string jsonIndent(indentLevel, 0x20);
629     const auto& callout = _callouts->callouts();
630     const auto& compDescrp = pv::failingComponentType;
631     printOut.append(jsonIndent + "\"Callout Section\": {\n");
632     jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
633     printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
634     for (auto& entry : callout)
635     {
636         printOut.append("{\n");
637         if (entry->fruIdentity())
638         {
639             jsonInsert(
640                 printOut, "FRU Type",
641                 compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
642             jsonInsert(printOut, "Priority",
643                        pv::getValue(entry->priority(),
644                                     pel_values::calloutPriorityValues),
645                        3);
646             if (!entry->locationCode().empty())
647             {
648                 jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
649             }
650             if (entry->fruIdentity()->getPN().has_value())
651             {
652                 jsonInsert(printOut, "Part Number",
653                            entry->fruIdentity()->getPN().value(), 3);
654             }
655             if (entry->fruIdentity()->getMaintProc().has_value())
656             {
657                 jsonInsert(printOut, "Procedure",
658                            entry->fruIdentity()->getMaintProc().value(), 3);
659                 if (pv::procedureDesc.find(
660                         entry->fruIdentity()->getMaintProc().value()) !=
661                     pv::procedureDesc.end())
662                 {
663                     jsonInsert(
664                         printOut, "Description",
665                         pv::procedureDesc.at(
666                             entry->fruIdentity()->getMaintProc().value()),
667                         3);
668                 }
669             }
670             if (entry->fruIdentity()->getCCIN().has_value())
671             {
672                 jsonInsert(printOut, "CCIN",
673                            entry->fruIdentity()->getCCIN().value(), 3);
674             }
675             if (entry->fruIdentity()->getSN().has_value())
676             {
677                 jsonInsert(printOut, "Serial Number",
678                            entry->fruIdentity()->getSN().value(), 3);
679             }
680         }
681         if (entry->pceIdentity())
682         {
683             const auto& pceIdentMtms = entry->pceIdentity()->mtms();
684             if (!pceIdentMtms.machineTypeAndModel().empty())
685             {
686                 jsonInsert(printOut, "PCE MTMS",
687                            pceIdentMtms.machineTypeAndModel() + "_" +
688                                pceIdentMtms.machineSerialNumber(),
689                            3);
690             }
691             if (!entry->pceIdentity()->enclosureName().empty())
692             {
693                 jsonInsert(printOut, "PCE Name",
694                            entry->pceIdentity()->enclosureName(), 3);
695             }
696         }
697         if (entry->mru())
698         {
699             const auto& mruCallouts = entry->mru()->mrus();
700             std::string mruId;
701             for (auto& element : mruCallouts)
702             {
703                 if (!mruId.empty())
704                 {
705                     mruId.append(", " + getNumberString("%08X", element.id));
706                 }
707                 else
708                 {
709                     mruId.append(getNumberString("%08X", element.id));
710                 }
711             }
712             jsonInsert(printOut, "MRU Id", mruId, 3);
713         }
714         printOut.erase(printOut.size() - 2);
715         printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
716     };
717     printOut.erase(printOut.size() - 2);
718     printOut.append("]\n" + jsonIndent + "}");
719     return printOut;
720 }
721 
722 std::optional<std::string> SRC::getJSON(message::Registry& registry,
723                                         const std::vector<std::string>& plugins
724                                         [[maybe_unused]],
725                                         uint8_t creatorID) const
726 {
727     std::string ps;
728     std::vector<std::string> hexwords;
729     jsonInsert(ps, pv::sectionVer, getNumberString("%d", _header.version), 1);
730     jsonInsert(ps, pv::subSection, getNumberString("%d", _header.subType), 1);
731     jsonInsert(ps, pv::createdBy,
732                getComponentName(_header.componentID, creatorID), 1);
733     jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
734     jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
735                1);
736     jsonInsert(ps, "Virtual Progress SRC",
737                pv::boolString.at(_flags & virtualProgressSRC), 1);
738     jsonInsert(ps, "I5/OS Service Event Bit",
739                pv::boolString.at(_flags & i5OSServiceEventBit), 1);
740     jsonInsert(ps, "Hypervisor Dump Initiated",
741                pv::boolString.at(_flags & hypDumpInit), 1);
742 
743     if (isBMCSRC())
744     {
745         std::string ccinString;
746         uint32_t ccin = _hexData[1] >> 16;
747 
748         if (ccin)
749         {
750             ccinString = getNumberString("%04X", ccin);
751         }
752         // The PEL spec calls it a backplane, so call it that here.
753         jsonInsert(ps, "Backplane CCIN", ccinString, 1);
754 
755         jsonInsert(ps, "Terminate FW Error",
756                    pv::boolString.at(
757                        _hexData[3] &
758                        static_cast<uint32_t>(ErrorStatusFlags::terminateFwErr)),
759                    1);
760     }
761 
762     if (isBMCSRC() || isHostbootSRC())
763     {
764         jsonInsert(ps, "Deconfigured",
765                    pv::boolString.at(
766                        _hexData[3] &
767                        static_cast<uint32_t>(ErrorStatusFlags::deconfigured)),
768                    1);
769 
770         jsonInsert(
771             ps, "Guarded",
772             pv::boolString.at(_hexData[3] &
773                               static_cast<uint32_t>(ErrorStatusFlags::guarded)),
774             1);
775     }
776 
777     auto errorDetails = getErrorDetails(registry, DetailLevel::json, true);
778     if (errorDetails)
779     {
780         ps.append(errorDetails.value());
781     }
782     jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
783                1);
784     std::string refcode = asciiString();
785     hexwords.push_back(refcode);
786     std::string extRefcode;
787     size_t pos = refcode.find(0x20);
788     if (pos != std::string::npos)
789     {
790         size_t nextPos = refcode.find_first_not_of(0x20, pos);
791         if (nextPos != std::string::npos)
792         {
793             extRefcode = trimEnd(refcode.substr(nextPos));
794         }
795         refcode.erase(pos);
796     }
797     jsonInsert(ps, "Reference Code", refcode, 1);
798     if (!extRefcode.empty())
799     {
800         jsonInsert(ps, "Extended Reference Code", extRefcode, 1);
801     }
802     for (size_t i = 2; i <= _wordCount; i++)
803     {
804         std::string tmpWord =
805             getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]);
806         jsonInsert(ps, "Hex Word " + std::to_string(i), tmpWord, 1);
807         hexwords.push_back(tmpWord);
808     }
809     auto calloutJson = getCallouts();
810     if (calloutJson)
811     {
812         ps.append(calloutJson.value());
813         ps.append(",\n");
814     }
815     std::string subsystem = getNumberString("%c", tolower(creatorID));
816     bool srcDetailExists = false;
817 #ifdef PELTOOL
818     if (std::find(plugins.begin(), plugins.end(), subsystem + "src") !=
819         plugins.end())
820     {
821         auto pyJson = getPythonJSON(hexwords, creatorID);
822         if (pyJson)
823         {
824             ps.append(pyJson.value());
825             srcDetailExists = true;
826         }
827     }
828 #endif
829     if (!srcDetailExists)
830     {
831         ps.erase(ps.size() - 2);
832     }
833     return ps;
834 }
835 
836 void SRC::addCallouts(const message::Entry& regEntry,
837                       const AdditionalData& additionalData,
838                       const nlohmann::json& jsonCallouts,
839                       const DataInterfaceBase& dataIface)
840 {
841     auto registryCallouts = getRegistryCallouts(regEntry, additionalData,
842                                                 dataIface);
843 
844     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
845     auto priority = additionalData.getValue("CALLOUT_PRIORITY");
846 
847     std::optional<CalloutPriority> calloutPriority;
848 
849     // Only  H, M or L priority values.
850     if (priority && !(*priority).empty())
851     {
852         uint8_t p = (*priority)[0];
853         if (p == 'H' || p == 'M' || p == 'L')
854         {
855             calloutPriority = static_cast<CalloutPriority>(p);
856         }
857     }
858     // If the first registry callout says to use the passed in inventory
859     // path to get the location code for a symbolic FRU callout with a
860     // trusted location code, then do not add the inventory path as a
861     // normal FRU callout.
862     bool useInvForSymbolicFRULocCode =
863         !registryCallouts.empty() && registryCallouts[0].useInventoryLocCode &&
864         !registryCallouts[0].symbolicFRUTrusted.empty();
865 
866     if (item && !useInvForSymbolicFRULocCode)
867     {
868         addInventoryCallout(*item, calloutPriority, std::nullopt, dataIface);
869     }
870 
871     addDevicePathCallouts(additionalData, dataIface);
872 
873     addRegistryCallouts(registryCallouts, dataIface,
874                         (useInvForSymbolicFRULocCode) ? item : std::nullopt);
875 
876     if (!jsonCallouts.empty())
877     {
878         addJSONCallouts(jsonCallouts, dataIface);
879     }
880 }
881 
882 void SRC::addInventoryCallout(const std::string& inventoryPath,
883                               const std::optional<CalloutPriority>& priority,
884                               const std::optional<std::string>& locationCode,
885                               const DataInterfaceBase& dataIface,
886                               const std::vector<src::MRU::MRUCallout>& mrus)
887 {
888     std::string locCode;
889     std::string fn;
890     std::string ccin;
891     std::string sn;
892     std::unique_ptr<src::Callout> callout;
893 
894     try
895     {
896         // Use the passed in location code if there otherwise look it up
897         if (locationCode)
898         {
899             locCode = *locationCode;
900         }
901         else
902         {
903             locCode = dataIface.getLocationCode(inventoryPath);
904         }
905 
906         try
907         {
908             dataIface.getHWCalloutFields(inventoryPath, fn, ccin, sn);
909 
910             CalloutPriority p = priority ? priority.value()
911                                          : CalloutPriority::high;
912 
913             callout = std::make_unique<src::Callout>(p, locCode, fn, ccin, sn,
914                                                      mrus);
915         }
916         catch (const sdbusplus::exception_t& e)
917         {
918             std::string msg = "No VPD found for " + inventoryPath + ": " +
919                               e.what();
920             addDebugData(msg);
921 
922             // Just create the callout with empty FRU fields
923             callout = std::make_unique<src::Callout>(
924                 CalloutPriority::high, locCode, fn, ccin, sn, mrus);
925         }
926     }
927     catch (const sdbusplus::exception_t& e)
928     {
929         std::string msg = "Could not get location code for " + inventoryPath +
930                           ": " + e.what();
931         addDebugData(msg);
932 
933         // Don't add a callout in this case, because:
934         // 1) With how the inventory is primed, there is no case where
935         //    a location code is expected to be missing.  This implies
936         //    the caller is passing in something invalid.
937         // 2) The addDebugData call above will put the passed in path into
938         //    a user data section that can be seen by development for debug.
939         // 3) Even if we wanted to do a 'no_vpd_for_fru' sort of maint.
940         //    procedure, we don't have a good way to indicate to the user
941         //    anything about the intended callout (they won't see user data).
942         // 4) Creating a new standalone event log for this problem isn't
943         //    possible from inside a PEL section.
944     }
945 
946     if (callout)
947     {
948         createCalloutsObject();
949         _callouts->addCallout(std::move(callout));
950     }
951 }
952 
953 std::vector<message::RegistryCallout>
954     SRC::getRegistryCallouts(const message::Entry& regEntry,
955                              const AdditionalData& additionalData,
956                              const DataInterfaceBase& dataIface)
957 {
958     std::vector<message::RegistryCallout> registryCallouts;
959 
960     if (regEntry.callouts)
961     {
962         std::vector<std::string> systemNames;
963 
964         try
965         {
966             systemNames = dataIface.getSystemNames();
967         }
968         catch (const std::exception& e)
969         {
970             // Compatible interface not available yet
971         }
972 
973         try
974         {
975             registryCallouts = message::Registry::getCallouts(
976                 regEntry.callouts.value(), systemNames, additionalData);
977         }
978         catch (const std::exception& e)
979         {
980             addDebugData(fmt::format(
981                 "Error parsing PEL message registry callout JSON: {}",
982                 e.what()));
983         }
984     }
985 
986     return registryCallouts;
987 }
988 
989 void SRC::addRegistryCallouts(
990     const std::vector<message::RegistryCallout>& callouts,
991     const DataInterfaceBase& dataIface,
992     std::optional<std::string> trustedSymbolicFRUInvPath)
993 {
994     try
995     {
996         for (const auto& callout : callouts)
997         {
998             addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
999 
1000             // Only the first callout gets the inventory path
1001             if (trustedSymbolicFRUInvPath)
1002             {
1003                 trustedSymbolicFRUInvPath = std::nullopt;
1004             }
1005         }
1006     }
1007     catch (const std::exception& e)
1008     {
1009         std::string msg = "Error parsing PEL message registry callout JSON: "s +
1010                           e.what();
1011         addDebugData(msg);
1012     }
1013 }
1014 
1015 void SRC::addRegistryCallout(
1016     const message::RegistryCallout& regCallout,
1017     const DataInterfaceBase& dataIface,
1018     const std::optional<std::string>& trustedSymbolicFRUInvPath)
1019 {
1020     std::unique_ptr<src::Callout> callout;
1021     auto locCode = regCallout.locCode;
1022 
1023     if (!locCode.empty())
1024     {
1025         try
1026         {
1027             locCode = dataIface.expandLocationCode(locCode, 0);
1028         }
1029         catch (const std::exception& e)
1030         {
1031             auto msg = "Unable to expand location code " + locCode + ": " +
1032                        e.what();
1033             addDebugData(msg);
1034             return;
1035         }
1036     }
1037 
1038     // Via the PEL values table, get the priority enum.
1039     // The schema will have validated the priority was a valid value.
1040     auto priorityIt = pv::findByName(regCallout.priority,
1041                                      pv::calloutPriorityValues);
1042     assert(priorityIt != pv::calloutPriorityValues.end());
1043     auto priority =
1044         static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
1045 
1046     if (!regCallout.procedure.empty())
1047     {
1048         // Procedure callout
1049         callout = std::make_unique<src::Callout>(priority,
1050                                                  regCallout.procedure);
1051     }
1052     else if (!regCallout.symbolicFRU.empty())
1053     {
1054         // Symbolic FRU callout
1055         callout = std::make_unique<src::Callout>(
1056             priority, regCallout.symbolicFRU, locCode, false);
1057     }
1058     else if (!regCallout.symbolicFRUTrusted.empty())
1059     {
1060         // Symbolic FRU with trusted location code callout
1061 
1062         // Use the location code from the inventory path if there is one.
1063         if (trustedSymbolicFRUInvPath)
1064         {
1065             try
1066             {
1067                 locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
1068             }
1069             catch (const std::exception& e)
1070             {
1071                 addDebugData(
1072                     fmt::format("Could not get location code for {}: {}",
1073                                 *trustedSymbolicFRUInvPath, e.what()));
1074                 locCode.clear();
1075             }
1076         }
1077 
1078         // The registry wants it to be trusted, but that requires a valid
1079         // location code for it to actually be.
1080         callout = std::make_unique<src::Callout>(
1081             priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
1082     }
1083     else
1084     {
1085         // A hardware callout
1086         std::vector<std::string> inventoryPaths;
1087 
1088         try
1089         {
1090             // Get the inventory item from the unexpanded location code
1091             inventoryPaths =
1092                 dataIface.getInventoryFromLocCode(regCallout.locCode, 0, false);
1093         }
1094         catch (const std::exception& e)
1095         {
1096             std::string msg =
1097                 "Unable to get inventory path from location code: " + locCode +
1098                 ": " + e.what();
1099             addDebugData(msg);
1100             return;
1101         }
1102 
1103         // Just use first path returned since they all point to the same FRU.
1104         addInventoryCallout(inventoryPaths[0], priority, locCode, dataIface);
1105     }
1106 
1107     if (callout)
1108     {
1109         createCalloutsObject();
1110         _callouts->addCallout(std::move(callout));
1111     }
1112 }
1113 
1114 void SRC::addDevicePathCallouts(const AdditionalData& additionalData,
1115                                 const DataInterfaceBase& dataIface)
1116 {
1117     std::vector<device_callouts::Callout> callouts;
1118     auto i2cBus = additionalData.getValue("CALLOUT_IIC_BUS");
1119     auto i2cAddr = additionalData.getValue("CALLOUT_IIC_ADDR");
1120     auto devPath = additionalData.getValue("CALLOUT_DEVICE_PATH");
1121 
1122     // A device callout contains either:
1123     // * CALLOUT_ERRNO, CALLOUT_DEVICE_PATH
1124     // * CALLOUT_ERRNO, CALLOUT_IIC_BUS, CALLOUT_IIC_ADDR
1125     // We don't care about the errno.
1126 
1127     if (devPath)
1128     {
1129         try
1130         {
1131             callouts = device_callouts::getCallouts(*devPath,
1132                                                     dataIface.getSystemNames());
1133         }
1134         catch (const std::exception& e)
1135         {
1136             addDebugData(e.what());
1137             callouts.clear();
1138         }
1139     }
1140     else if (i2cBus && i2cAddr)
1141     {
1142         size_t bus;
1143         uint8_t address;
1144 
1145         try
1146         {
1147             // If /dev/i2c- is prepended, remove it
1148             if (i2cBus->find("/dev/i2c-") != std::string::npos)
1149             {
1150                 *i2cBus = i2cBus->substr(9);
1151             }
1152 
1153             bus = stoul(*i2cBus, nullptr, 0);
1154             address = stoul(*i2cAddr, nullptr, 0);
1155         }
1156         catch (const std::exception& e)
1157         {
1158             std::string msg = "Invalid CALLOUT_IIC_BUS " + *i2cBus +
1159                               " or CALLOUT_IIC_ADDR " + *i2cAddr +
1160                               " in AdditionalData property";
1161             addDebugData(msg);
1162             return;
1163         }
1164 
1165         try
1166         {
1167             callouts = device_callouts::getI2CCallouts(
1168                 bus, address, dataIface.getSystemNames());
1169         }
1170         catch (const std::exception& e)
1171         {
1172             addDebugData(e.what());
1173             callouts.clear();
1174         }
1175     }
1176 
1177     for (const auto& callout : callouts)
1178     {
1179         // The priority shouldn't be invalid, but check just in case.
1180         CalloutPriority priority = CalloutPriority::high;
1181 
1182         if (!callout.priority.empty())
1183         {
1184             auto p = pel_values::findByValue(
1185                 static_cast<uint32_t>(callout.priority[0]),
1186                 pel_values::calloutPriorityValues);
1187 
1188             if (p != pel_values::calloutPriorityValues.end())
1189             {
1190                 priority = static_cast<CalloutPriority>(callout.priority[0]);
1191             }
1192             else
1193             {
1194                 std::string msg =
1195                     "Invalid priority found in dev callout JSON: " +
1196                     callout.priority[0];
1197                 addDebugData(msg);
1198             }
1199         }
1200 
1201         std::optional<std::string> locCode;
1202 
1203         try
1204         {
1205             locCode = dataIface.expandLocationCode(callout.locationCode, 0);
1206         }
1207         catch (const std::exception& e)
1208         {
1209             auto msg = fmt::format("Unable to expand location code {}: {}",
1210                                    callout.locationCode, e.what());
1211             addDebugData(msg);
1212         }
1213 
1214         try
1215         {
1216             auto inventoryPaths = dataIface.getInventoryFromLocCode(
1217                 callout.locationCode, 0, false);
1218 
1219             // Just use first path returned since they all
1220             // point to the same FRU.
1221             addInventoryCallout(inventoryPaths[0], priority, locCode,
1222                                 dataIface);
1223         }
1224         catch (const std::exception& e)
1225         {
1226             std::string msg =
1227                 "Unable to get inventory path from location code: " +
1228                 callout.locationCode + ": " + e.what();
1229             addDebugData(msg);
1230         }
1231 
1232         // Until the code is there to convert these MRU value strings to
1233         // the official MRU values in the callout objects, just store
1234         // the MRU name in the debug UserData section.
1235         if (!callout.mru.empty())
1236         {
1237             std::string msg = "MRU: " + callout.mru;
1238             addDebugData(msg);
1239         }
1240 
1241         // getCallouts() may have generated some debug data it stored
1242         // in a callout object.  Save it as well.
1243         if (!callout.debug.empty())
1244         {
1245             addDebugData(callout.debug);
1246         }
1247     }
1248 }
1249 
1250 void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
1251                           const DataInterfaceBase& dataIface)
1252 {
1253     if (jsonCallouts.empty())
1254     {
1255         return;
1256     }
1257 
1258     if (!jsonCallouts.is_array())
1259     {
1260         addDebugData("Callout JSON isn't an array");
1261         return;
1262     }
1263 
1264     for (const auto& callout : jsonCallouts)
1265     {
1266         try
1267         {
1268             addJSONCallout(callout, dataIface);
1269         }
1270         catch (const std::exception& e)
1271         {
1272             addDebugData(fmt::format(
1273                 "Failed extracting callout data from JSON: {}", e.what()));
1274         }
1275     }
1276 }
1277 
1278 void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
1279                          const DataInterfaceBase& dataIface)
1280 {
1281     auto priority = getPriorityFromJSON(jsonCallout);
1282     std::string locCode;
1283     std::string unexpandedLocCode;
1284     std::unique_ptr<src::Callout> callout;
1285 
1286     // Expand the location code if it's there
1287     if (jsonCallout.contains("LocationCode"))
1288     {
1289         unexpandedLocCode = jsonCallout.at("LocationCode").get<std::string>();
1290 
1291         try
1292         {
1293             locCode = dataIface.expandLocationCode(unexpandedLocCode, 0);
1294         }
1295         catch (const std::exception& e)
1296         {
1297             addDebugData(fmt::format("Unable to expand location code {}: {}",
1298                                      unexpandedLocCode, e.what()));
1299             // Use the value from the JSON so at least there's something
1300             locCode = unexpandedLocCode;
1301         }
1302     }
1303 
1304     // Create either a procedure, symbolic FRU, or normal FRU callout.
1305     if (jsonCallout.contains("Procedure"))
1306     {
1307         auto procedure = jsonCallout.at("Procedure").get<std::string>();
1308 
1309         // If it's the registry name instead of the raw name, convert.
1310         if (pv::maintenanceProcedures.find(procedure) !=
1311             pv::maintenanceProcedures.end())
1312         {
1313             procedure = pv::maintenanceProcedures.at(procedure);
1314         }
1315 
1316         callout = std::make_unique<src::Callout>(
1317             static_cast<CalloutPriority>(priority), procedure,
1318             src::CalloutValueType::raw);
1319     }
1320     else if (jsonCallout.contains("SymbolicFRU"))
1321     {
1322         auto fru = jsonCallout.at("SymbolicFRU").get<std::string>();
1323 
1324         // If it's the registry name instead of the raw name, convert.
1325         if (pv::symbolicFRUs.find(fru) != pv::symbolicFRUs.end())
1326         {
1327             fru = pv::symbolicFRUs.at(fru);
1328         }
1329 
1330         bool trusted = false;
1331         if (jsonCallout.contains("TrustedLocationCode") && !locCode.empty())
1332         {
1333             trusted = jsonCallout.at("TrustedLocationCode").get<bool>();
1334         }
1335 
1336         callout = std::make_unique<src::Callout>(
1337             static_cast<CalloutPriority>(priority), fru,
1338             src::CalloutValueType::raw, locCode, trusted);
1339     }
1340     else
1341     {
1342         // A hardware FRU
1343         std::string inventoryPath;
1344         std::vector<src::MRU::MRUCallout> mrus;
1345 
1346         if (jsonCallout.contains("InventoryPath"))
1347         {
1348             inventoryPath = jsonCallout.at("InventoryPath").get<std::string>();
1349         }
1350         else
1351         {
1352             if (unexpandedLocCode.empty())
1353             {
1354                 throw std::runtime_error{"JSON callout needs either an "
1355                                          "inventory path or location code"};
1356             }
1357 
1358             try
1359             {
1360                 auto inventoryPaths = dataIface.getInventoryFromLocCode(
1361                     unexpandedLocCode, 0, false);
1362                 // Just use first path returned since they all
1363                 // point to the same FRU.
1364                 inventoryPath = inventoryPaths[0];
1365             }
1366             catch (const std::exception& e)
1367             {
1368                 throw std::runtime_error{
1369                     fmt::format("Unable to get inventory path from "
1370                                 "location code: {}: {}",
1371                                 unexpandedLocCode, e.what())};
1372             }
1373         }
1374 
1375         if (jsonCallout.contains("MRUs"))
1376         {
1377             mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
1378         }
1379 
1380         // If the location code was also passed in, use that here too
1381         // so addInventoryCallout doesn't have to look it up.
1382         std::optional<std::string> lc;
1383         if (!locCode.empty())
1384         {
1385             lc = locCode;
1386         }
1387 
1388         addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
1389 
1390         if (jsonCallout.contains("Deconfigured"))
1391         {
1392             if (jsonCallout.at("Deconfigured").get<bool>())
1393             {
1394                 setErrorStatusFlag(ErrorStatusFlags::deconfigured);
1395             }
1396         }
1397 
1398         if (jsonCallout.contains("Guarded"))
1399         {
1400             if (jsonCallout.at("Guarded").get<bool>())
1401             {
1402                 setErrorStatusFlag(ErrorStatusFlags::guarded);
1403             }
1404         }
1405     }
1406 
1407     if (callout)
1408     {
1409         createCalloutsObject();
1410         _callouts->addCallout(std::move(callout));
1411     }
1412 }
1413 
1414 CalloutPriority SRC::getPriorityFromJSON(const nlohmann::json& json)
1415 {
1416     // Looks like:
1417     // {
1418     //     "Priority": "H"
1419     // }
1420     auto p = json.at("Priority").get<std::string>();
1421     if (p.empty())
1422     {
1423         throw std::runtime_error{"Priority field in callout is empty"};
1424     }
1425 
1426     auto priority = static_cast<CalloutPriority>(p.front());
1427 
1428     // Validate it
1429     auto priorityIt = pv::findByValue(static_cast<uint32_t>(priority),
1430                                       pv::calloutPriorityValues);
1431     if (priorityIt == pv::calloutPriorityValues.end())
1432     {
1433         throw std::runtime_error{
1434             fmt::format("Invalid priority '{}' found in JSON callout", p)};
1435     }
1436 
1437     return priority;
1438 }
1439 
1440 std::vector<src::MRU::MRUCallout>
1441     SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
1442 {
1443     std::vector<src::MRU::MRUCallout> mrus;
1444 
1445     // Looks like:
1446     // [
1447     //     {
1448     //         "ID": 100,
1449     //         "Priority": "H"
1450     //     }
1451     // ]
1452     if (!mruJSON.is_array())
1453     {
1454         addDebugData("MRU callout JSON is not an array");
1455         return mrus;
1456     }
1457 
1458     for (const auto& mruCallout : mruJSON)
1459     {
1460         try
1461         {
1462             auto priority = getPriorityFromJSON(mruCallout);
1463             auto id = mruCallout.at("ID").get<uint32_t>();
1464 
1465             src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
1466             mrus.push_back(std::move(mru));
1467         }
1468         catch (const std::exception& e)
1469         {
1470             addDebugData(fmt::format("Invalid MRU entry in JSON: {}: {}",
1471                                      mruCallout.dump(), e.what()));
1472         }
1473     }
1474 
1475     return mrus;
1476 }
1477 
1478 void SRC::setDumpStatus(const DataInterfaceBase& dataIface)
1479 {
1480     std::vector<bool> dumpStatus{false, false, false};
1481 
1482     try
1483     {
1484         std::vector<std::string> dumpType = {"bmc/entry", "resource/entry",
1485                                              "system/entry"};
1486         dumpStatus = dataIface.checkDumpStatus(dumpType);
1487 
1488         // For bmc      - set bit 0 of nibble [4-7] bits of byte-1 SP dump
1489         // For resource - set bit 2 of nibble [4-7] bits of byte-2 Hypervisor
1490         // For system   - set bit 1 of nibble [4-7] bits of byte-2 HW dump
1491         _hexData[0] |= ((dumpStatus[0] << 19) | (dumpStatus[1] << 9) |
1492                         (dumpStatus[2] << 10));
1493     }
1494     catch (const std::exception& e)
1495     {
1496         log<level::ERR>(
1497             fmt::format("Checking dump status failed: {}", e.what()).c_str());
1498     }
1499 }
1500 
1501 std::vector<uint8_t> SRC::getSrcStruct()
1502 {
1503     std::vector<uint8_t> data;
1504     Stream stream{data};
1505 
1506     //------ Ref section 4.3 in PEL doc---
1507     //------ SRC Structure 40 bytes-------
1508     // Byte-0 | Byte-1 | Byte-2 | Byte-3 |
1509     // -----------------------------------
1510     //   02   |   08   |   00   |   09   | ==> Header
1511     //   00   |   00   |   00   |   48   | ==> Header
1512     //   00   |   00   |   00   |   00   | ==> Hex data word-2
1513     //   00   |   00   |   00   |   00   | ==> Hex data word-3
1514     //   00   |   00   |   00   |   00   | ==> Hex data word-4
1515     //   20   |   00   |   00   |   00   | ==> Hex data word-5
1516     //   00   |   00   |   00   |   00   | ==> Hex data word-6
1517     //   00   |   00   |   00   |   00   | ==> Hex data word-7
1518     //   00   |   00   |   00   |   00   | ==> Hex data word-8
1519     //   00   |   00   |   00   |   00   | ==> Hex data word-9
1520     // -----------------------------------
1521     //   ASCII string - 8 bytes          |
1522     // -----------------------------------
1523     //   ASCII space NULL - 24 bytes     |
1524     // -----------------------------------
1525     //_size = Base SRC struct: 8 byte header + hex data section + ASCII string
1526 
1527     uint8_t flags = (_flags | postOPPanel);
1528 
1529     stream << _version << flags << _reserved1B << _wordCount << _reserved2B
1530            << _size;
1531 
1532     for (auto& word : _hexData)
1533     {
1534         stream << word;
1535     }
1536 
1537     _asciiString->flatten(stream);
1538 
1539     return data;
1540 }
1541 
1542 void SRC::setProgressCode(const DataInterfaceBase& dataIface)
1543 {
1544     std::vector<uint8_t> progressSRC;
1545 
1546     try
1547     {
1548         progressSRC = dataIface.getRawProgressSRC();
1549     }
1550     catch (const std::exception& e)
1551     {
1552         log<level::ERR>(
1553             fmt::format("Error getting progress code: {}", e.what()).c_str());
1554         return;
1555     }
1556 
1557     _hexData[2] = getProgressCode(progressSRC);
1558 }
1559 
1560 uint32_t SRC::getProgressCode(std::vector<uint8_t>& rawProgressSRC)
1561 {
1562     uint32_t progressCode = 0;
1563 
1564     // A valid progress SRC is at least 72 bytes
1565     if (rawProgressSRC.size() < 72)
1566     {
1567         return progressCode;
1568     }
1569 
1570     try
1571     {
1572         // The ASCII string field in progress SRCs starts at offset 40.
1573         // Take the first 8 characters to put in the uint32:
1574         //   "CC009189" -> 0xCC009189
1575         Stream stream{rawProgressSRC, 40};
1576         src::AsciiString aString{stream};
1577         auto progressCodeString = aString.get().substr(0, 8);
1578 
1579         if (std::all_of(progressCodeString.begin(), progressCodeString.end(),
1580                         [](char c) {
1581             return std::isxdigit(static_cast<unsigned char>(c));
1582             }))
1583         {
1584             progressCode = std::stoul(progressCodeString, nullptr, 16);
1585         }
1586     }
1587     catch (const std::exception& e)
1588     {}
1589 
1590     return progressCode;
1591 }
1592 
1593 } // namespace pels
1594 } // namespace openpower
1595