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