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