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