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